[WPF自定义控件库] 让Form在加载后自动获得焦点
原文:[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
函数。所谓的“第一个”,基本上和用户直觉上理解的一致。
DesignerProperties.GetIsInDesignMode
DesignerProperties.GetIsInDesignMode方法用于确定元素是否运行在设计器中。VisualStudio的设计器太过强大,几乎是所见即所得,大部分代码都可以在设计视图里运行。OnTargetLoaded里判断如果是运行在设计器就不执行后面的操作,是避免每次刷新设计视图都让它获得焦点。
VisualStudio的设计器真的十分强大,但有时又会因为程序的数据没准备好或各种原因而报错,如果遇到设计器的错误又不想处理具体原因可以考虑简单粗暴地使用DesignerProperties.GetIsInDesignMode
判断并直接return。
3. 两种焦点类型
作为补充知识,这篇文章将简单介绍一下WPF的焦点。
3.1 键盘焦点
键盘焦点指当前正在接收键盘输入的UI元素。 在整个桌面上,只能有一个具有键盘焦点的元素。为了使UI元素可以获得焦点,它的Focusable和IsVisible必须为True。通常,对于非控件类Focusable属性值的默认值为False。
Keyboard类可以用于处理键盘焦点,代码如下:
Keyboard.Focus(FirstTextBox);
Focus函数如果执行成功,UI元素的IsKeyboardFocused将被设置为True,并且它本身或VisualTree上各级父元素的IsKeyboardFocusWithin都会变成True。
当然,如果UI元素并未加载到VisualTree上Focus函数不会执行成功,所以通常在Loaded事件以后才执行Focus函数。
3.2 逻辑焦点
逻辑焦点是指FocusScope中的FocusManager.FocusedElement,一个应用程序中可以有多个获得逻辑焦点的元素,但只有一个获得键盘焦点的元素。获得键盘焦点的元素同时也获得逻辑焦点。
FocusScope
FocusScope可以通过FocusManager.IsFocusScope改变。
<StackPanel Name="focusScope1"
FocusManager.IsFocusScope="True"
Height="200" Width="200">
<Button Name="button1" Height="50" Width="50"/>
<Button Name="button2" Height="50" Width="50"/>
</StackPanel>
StackPanel focuseScope2 = new StackPanel();
FocusManager.SetIsFocusScope(focuseScope2, true);
FocusedElement
FocusManager还用于管理逻辑焦点,它使用GetFocusedElement(DependencyObject)获取FocusScope中获得逻辑焦点的元素,使用SetFocusedElement(DependencyObject, IInputElement)将元素设置为逻辑焦点。
3.3 Window的逻辑焦点
Window默认为FocusScope,它在静态构造函数中将IsFocusScope设置为True(不在DefaultStyle中设置):
FocusManager.IsFocusScopeProperty.OverrideMetadata(typeof(Window), new FrameworkPropertyMetadata(true));
在Window加载(或者Window本身被激活)时,它都会用类似的代码让Window中的逻辑焦点元素获得焦点。
DependencyObject doContent = Content as DependencyObject;
if (doContent != null)
{
IInputElement focusedElement = FocusManager.GetFocusedElement(doContent) as IInputElement;
if (focusedElement != null)
focusedElement.Focus();
}
4. 结语
其实没有这个类也可以,反正代码简单,只是想通过这个类介绍下附加属性和Focus的用法。
做自定义控件要做好焦点管理,尤其是现在,因为很多设计师、产品经理、开发者都有丰富的手机应用开发设计经验,由于手机上的键盘导航逻辑和桌面应用的有些出入,所以键盘导航的细节很容易被忽视。
不过,通常来说用着用着觉得不顺手就会有人提出需求,细心的开发者总会渐渐把键盘导航做好。
5. 参考
FocusManager Class (System.Windows.Input) Microsoft Docs
Keyboard.Focus(IInputElement) Method (System.Windows.Input) Microsoft Docs
UIElement.MoveFocus(TraversalRequest) Method (System.Windows) Microsoft Docs
6. 源码
Kino.Toolkit.Wpf_FocusService.cs
[WPF自定义控件库] 让Form在加载后自动获得焦点的更多相关文章
- [WPF 自定义控件]让Form在加载后自动获得焦点
1. 需求 加载后让第一个输入框或者焦点是个很基本的功能,典型的如"登录"对话框.一般来说"登录"对话框加载后"用户名"应该马上获得焦点,用 ...
- jquery-事件之页面框架加载后自动执行
jQuery事件之页面框架加载后自动执行 1)概述 HTML执行是按自上而下编译,而<script>一般写在body结束之前.如果在HTML加载的过程中卡住, 比如加载图片等,没有显示出来 ...
- [WPF自定义控件库]为Form和自定义Window添加FunctionBar
1. 前言 我常常看到同一个应用程序中的表单的按钮----也就是"确定"."取消"那两个按钮----实现得千奇百怪,其实只要使用统一的Style起码就可以统一按 ...
- Angular页面加载后自动弹窗
首先在控制器内写好一个弹窗,我用的是ionic的默认提示对话框 // 一个确认对话框 $scope.showConfirm = function() { var confirmPopup = $ion ...
- WPF防止界面卡死并显示加载中效果
原文:WPF防止界面卡死并显示加载中效果 网上貌似没有完整的WPF正在加载的例子,所以自己写了一个,希望能帮到有需要的同学 前台: <Window x:Class="WpfApplic ...
- WPF 如何创建自己的WPF自定义控件库
在我们平时的项目中,我们经常需要一套自己的自定义控件库,这个特别是在Prism这种框架下面进行开发的时候,每个人都使用一套统一的控件,这样才不会每个人由于界面不统一而造成的整个软件系统千差万别,所以我 ...
- 第一百五十七节,封装库--JavaScript,预加载图片
封装库--JavaScript,预加载图片 首先了解一个Image对象,为图片对象 Image对象 var temp_img = new Image(); //创建一个临时区域的图片对象alert ...
- [WPF自定义控件库] 关于ScrollViewer和滚动轮劫持(scroll-wheel-hijack)
原文:[WPF自定义控件库] 关于ScrollViewer和滚动轮劫持(scroll-wheel-hijack) 1. 什么是滚动轮劫持# 这篇文章介绍一个很简单的继承自ScrollViewer的控件 ...
- [WPF自定义控件库]使用WindowChrome自定义RibbonWindow
原文:[WPF自定义控件库]使用WindowChrome自定义RibbonWindow 1. 为什么要自定义RibbonWindow 自定义Window有可能是设计或功能上的要求,可以是非必要的,而自 ...
随机推荐
- bzoj1031题解
[解题思路] 将原串复制一份拼接到原串后作为处理串,可以对处理串的前一半后缀排序,即可得出顺序.复杂度O(Llog2L). [参考代码] 也是naive的时候写的..后缀数组居然是用桶排求的.. #p ...
- 40 VSCode下.json文件的编写——(1) linux/g++ (2).json中参数与预定义变量的意义解释
0 引言 转入linux/VSCode编程之后,迫切了解到有必有较为系统地学习一下VSCode中相关配置文件的写法.下面将分为 linux/g++编译指令..json文件关键词/替换变量的意义.编译链 ...
- NX二次开发-UFUN初始化UF_initialize
在调用UFUN函数时必须加Uf.h头文件,代码开头和结尾加UF_initialize和UF_terminate NX9+VS2012 #include <uf.h> #include &l ...
- NX二次开发-UFUN工程图表格注释section转tag函数UF_TABNOT_ask_tabular_note_of_section
NX9+VS2012 #include <uf.h> #include <uf_tabnot.h> #include <NXOpen/Part.hxx> #incl ...
- UVA-10200-Prime Time-判断素数个数(打表预处理)+精度控制
题意: 给出a.b区间,判断区间内素数所占百分比 思路: 注意提前打表和控制精度1e-8的范围足够用了 细节: 精度的处理 判断素数的方法(且返回值为bool) 数据类型的强制转换 保存素数个数 提前 ...
- 拾遗:{rpm、yum及源码方式管理软件包}
一.yum配置文件位置 /etc/yum.conf /etc/yum.repos.d/*.repo 二.yum常用命令 install pkgs reinstall pkgs update pkgs ...
- 剑指offer——24链表中倒数第k个结点
题目描述 输入一个链表,输出该链表中倒数第k个结点. 题解: 1.普通解法,先遍历一遍计算链表长度,然后遍历到倒数第k个节点: 2.只遍历一遍,使用双指针,使得头尾指针位差为k,那么当尾指针为空时 ...
- rtsp+rtmp多路流媒体播放
一.前言 之前博主有写过 一篇博文,讲的是使用videojs在谷歌浏览器网页上播放rtmp流媒体,具体可参考我之前的博客:https://www.cnblogs.com/FHC1994/p/99814 ...
- Number浮点数运算详解
文章来自我的 github 博客,包括技术输出和学习笔记,欢迎star. 一道题 0.1 + 0.2 = ? 在浏览器中测试下计算结果,得到的结果是 0.30000000000000004,并不是理想 ...
- sql 特殊时间值 第一天或最后一天 无计算错误
DECLARE @dt datetimeSET @dt=GETDATE() DECLARE @number intSET @number=3 --1.指定日期该年的第一天或最后一天--A. 年的第一天 ...