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
相关产品推荐
相关产品推荐

