You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Wpf移除ItemsControl项后,无需后台代码如何让焦点返回UserControl?

问题描述

主窗口包含一个UserControl,其内部ItemsControl的项可通过点击“x”按钮移除。初始键盘焦点已设置为该UserControl,但移除项后焦点会转移至主窗口,而非保留在UserControl内。需要找到无需手动编写焦点存储、恢复代码的解决方案,避免在大量按钮中重复这类逻辑。

复现代码

MyControl.xaml

<UserControl x:Class="WpfApp28.MyControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid Width="300">
        <StackPanel>
            <ItemsControl ItemsSource="{Binding}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Grid>
                            <TextBlock Text="{Binding}" />
                            <Button Content="x"
                                    Width="20"
                                    HorizontalAlignment="Right"
                                    VerticalAlignment="Center"
                                    Click="Button_Click" />
                        </Grid>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </StackPanel>
    </Grid>
</UserControl>

MyControl.xaml.cs

public partial class MyControl : UserControl
{
    public MyControl()
    {
        InitializeComponent();
        Focusable = true;
        Loaded += MyControl_Loaded;
    }

    private void MyControl_Loaded(object sender, RoutedEventArgs e)
    {
        Keyboard.Focus(this);
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        if (sender is FrameworkElement fe && fe.DataContext is string item)
        {
            (DataContext as ObservableCollection<string>).Remove(item);
        }
    }
}

MainWindow.xaml

<Window x:Class="WpfApp28.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApp28"
    Title="MainWindow" Height="450" Width="800">
<local:MyControl DataContext="{Binding Items}" />
</Window>

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    public ObservableCollection<string> Items { get; } = new ObservableCollection<string>();

    public MainWindow()
    {
        Items.Add("hello");
        Items.Add("there");
        Items.Add("world");

        DataContext = this;
        InitializeComponent();

        DispatcherTimer t = new DispatcherTimer();
        t.Interval = TimeSpan.FromMilliseconds(250);
        t.Tick += T_Tick;
        t.Start();
    }

    private void T_Tick(object? sender, EventArgs e)
    {
        Title = Keyboard.FocusedElement?.GetType().ToString() ?? "NULL";
    }
}
解决方案

方案1:通过FocusManager锁定焦点到UserControl

无需额外后台代码,仅修改UserControl的XAML,在根元素上设置FocusManager.FocusedElement绑定到自身,强制焦点始终处于UserControl范围内。当移除项导致原焦点元素销毁时,焦点会自动回到UserControl,不会转移到主窗口。

修改后的MyControl.xaml:

<UserControl x:Class="WpfApp28.MyControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             Focusable="True">
    <Grid Width="300" 
          FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}">
        <StackPanel>
            <ItemsControl ItemsSource="{Binding}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Grid>
                            <TextBlock Text="{Binding}" />
                            <Button Content="x"
                                    Width="20"
                                    HorizontalAlignment="Right"
                                    VerticalAlignment="Center"
                                    Click="Button_Click" />
                        </Grid>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </StackPanel>
    </Grid>
</UserControl>

方案2:自定义附加属性实现全局焦点复用

如果多个控件需要保持焦点的行为,可创建一个通用附加属性,只需编写一次代码,所有目标控件都能复用。该属性会在控件失去焦点且新焦点不在其可视化树内时,自动重新聚焦。

附加属性代码

public static class FocusRetention
{
    public static readonly DependencyProperty KeepFocusProperty =
        DependencyProperty.RegisterAttached("KeepFocus", typeof(bool), typeof(FocusRetention), new PropertyMetadata(false, OnKeepFocusChanged));

    public static bool GetKeepFocus(DependencyObject obj)
    {
        return (bool)obj.GetValue(KeepFocusProperty);
    }

    public static void SetKeepFocus(DependencyObject obj, bool value)
    {
        obj.SetValue(KeepFocusProperty, value);
    }

    private static void OnKeepFocusChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is UIElement element && (bool)e.NewValue)
        {
            element.LostKeyboardFocus += Element_LostKeyboardFocus;
        }
        else if (d is UIElement elementOld)
        {
            elementOld.LostKeyboardFocus -= Element_LostKeyboardFocus;
        }
    }

    private static void Element_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
    {
        if (sender is UIElement element && !element.IsAncestorOf(e.NewFocus as DependencyObject))
        {
            Dispatcher.BeginInvoke(() => Keyboard.Focus(element));
        }
    }
}

使用附加属性

在需要保持焦点的UserControl上添加该属性:

<UserControl x:Class="WpfApp28.MyControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WpfApp28"
             Focusable="True"
             local:FocusRetention.KeepFocus="True">
    <!-- 原有内容不变 -->
</UserControl>

方案3:让ItemsControl容器保留焦点

修改ItemsControl的容器样式,使容器成为可聚焦元素,并将按钮设置为不可聚焦。这样点击按钮时不会夺走焦点,移除项时焦点会自动转移到剩余的容器或UserControl内。

修改后的ItemsControl代码

<ItemsControl ItemsSource="{Binding}">
    <ItemsControl.ItemContainerStyle>
        <Style TargetType="ContentPresenter">
            <Setter Property="Focusable" Value="True"/>
        </Style>
    </ItemsControl.ItemContainerStyle>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Grid>
                <TextBlock Text="{Binding}" />
                <Button Content="x"
                        Width="20"
                        HorizontalAlignment="Right"
                        VerticalAlignment="Center"
                        Click="Button_Click"
                        Focusable="False"/>
            </Grid>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>
总结
  • 方案1是最简洁的无代码方案,适合单个控件的场景;
  • 方案2适合大量控件需要复用焦点保持逻辑的场景,仅需一次编码;
  • 方案3适合希望焦点留在列表项容器内的场景。

内容的提问来源于stack exchange,提问作者user13251051

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 01:01:23