[WPF自定义控件库] 让Form在加载后自动获得焦点
1. 需求#
加载后让第一个输入框或者焦点是个很基本的功能,典型的如“登录”对话框。一般来说“登录”对话框加载后“用户名”应该马上获得焦点,用户只需输入用户名,点击Tab
,再输入密码,点击回车就完成了登录操作。
在WPF中要让一个控件在加载时获得焦点应该很简单,只需要在Loaded事件后调用Focus()
就行了。但有时表单是动态添加的,或者第一个表单元素会根据某些条件显示或隐藏,这时很难简单地让第一个控件获得焦点。
为了实现这个功能我创建了一个叫FocusService的工具类,这篇文章介绍这个类的使用及原理,以及补充一些WPF焦点的知识。
2. 实现#
public static readonly DependencyProperty IsAutoFocusProperty = DependencyProperty.RegisterAttached("IsAutoFocus", typeof(bool), typeof(FocusService), new PropertyMetadata(default(bool), OnIsAutoFocusChanged)); public static bool GetIsAutoFocus(DependencyObject obj) => (bool)obj.GetValue(IsAutoFocusProperty); public static void SetIsAutoFocus(DependencyObject obj, bool value) => obj.SetValue(IsAutoFocusProperty, value); private static void OnIsAutoFocusChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) { var oldValue = (bool)args.OldValue; var newValue = (bool)args.NewValue; if (oldValue == newValue) { return; } if (obj is FrameworkElement target) { target.Loaded -= OnTargetLoaded; if (newValue) { target.Loaded += OnTargetLoaded; } } } private static void OnTargetLoaded(object sender, RoutedEventArgs e) { var element = sender as FrameworkElement; if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(element)) return; var request = new TraversalRequest(FocusNavigationDirection.Next); element.MoveFocus(request); }
上面是FocusService的代码,它使用IsAutoFocus这个附加属性控制是否自动获得焦点,做成附加属性是为了可在XAML上控制。这个附加属性不仅可以用在Control上,还可以用在Grid等其它UI元素上。在Form中是在DefaultStyle设用Setter设置了默认值,以前提过一般情况下附加属性和依赖属性都不会在代码里设置默认值。
<Setter Property="local:FocusService.IsAutoFocus" Value="True" />
MoveFocus#
在FrameworkElement上将IsAutoFocus
附加属性设置为True的话(False不处理),这个FrameworkElement会在Loaded事件调用MoveFocus函数将键盘焦点移动到自身VisualTree中第一个可以接受焦点的元素上。大致上,MoveFocus的具体操作是使用深度优先的方式遍历VisualTree,找到第一个IsTabStob、Focusable和IsVisible都为True的元素并调用Keyboard.Focus
函数。所