[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有可能是设计或功能上的要求,可以是非必要的,而自 ...
随机推荐
- 更强大的实时数仓构建能力!分析型数据库PostgreSQL 6.0新特性解读
阿里云 AnalyticDB for PostgreSQL 为采用MPP架构的分布式集群数据库,完备支持SQL 2003,部分兼容Oracle语法,支持PL/SQL存储过程,触发器,支持标准数据库事务 ...
- 云栖干货回顾 | 云原生数据库POLARDB专场“硬核”解析
POLARDB是阿里巴巴自主研发的云原生关系型数据库,目前兼容三种数据库引擎:MySQL.PostgreSQL.Oracle.POLARDB的计算能力最高可扩展至1000核以上,存储容量可达100TB ...
- vue中利用scss实现整体换肤和字体大小设置
一.前言 利用Sass预处理实现换肤和字体大小调整. 思路及达到的效果:字体大小的适配使用window.devicePixelRatio的值和需要调整的差量进行控制.页面初始化是的字体适配可以根据de ...
- 知识整理:字符串hash
字符串hash唯一用途是快速判断两字符串是否相等,但存在极小概率假阳性(本来不相等,但算法返回相等). 根本思想是把一个字符串转换为一个整数,要求相同的字符串,对应的这个整数相同,不同的字符串,对应的 ...
- Python实现二叉堆
Python实现二叉堆 二叉堆是一种特殊的堆,二叉堆是完全二元树(二叉树)或者是近似完全二元树(二叉树).二叉堆有两种:最大堆和最小堆.最大堆:父结点的键值总是大于或等于任何一个子节点的键值:最小堆: ...
- HDFS API 操作实例(一) HDFS读写操作
1. 读取HDFS文件 1.1 字符读取HDFS上的文件 Configuration conf = new Configuration(); Path path = new Path(pathstr) ...
- USACO2008 Patting Heads /// 筛数 oj24705
题目大意: N (1 < N < 100,000)头牛被编号为1-N,围坐成圈 每头牛都被画上数字Ai (1 ≤ Ai ≤ 1,000,000),可能重复 逐个起来拍打 其他身上的数字是 ...
- 关于ctype.h头文件使用说明
ctype.h里的函数概况: 1.字符测试函数 (1)函数原型均为 int isXXX( int ch) (2)参数为int,任何参数均被转换为整形 (3)只能处理[0,127]之间的值 2.字符映射 ...
- ubuntu自带截图工具
ubuntu自带的截图工具感觉能够满足基本的截图功能,可以不必安装另外的截图软件. 一般用到的截图类型有三种:全屏.当前活动窗口.自定义区域,其中自定义区域截图是最灵活也是我们用的最多的方式.在ubu ...
- Python学习笔记(五)——异常处理
Python 异常总结 异常名称 解释 AssertionError 断言语句(assert)失败:当assert关键字后边的条件为假时,程序将抛出该异常,一般用于在代码中置入检查点 OSError ...