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. 参考

焦点概述 Microsoft Docs

输入概述 Microsoft Docs

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在加载后自动获得焦点的更多相关文章

  1. [WPF自定义控件库] 让Form在加载后自动获得焦点

    原文:[WPF自定义控件库] 让Form在加载后自动获得焦点 1. 需求 加载后让第一个输入框或者焦点是个很基本的功能,典型的如"登录"对话框.一般来说"登录" ...

  2. jquery-事件之页面框架加载后自动执行

    jQuery事件之页面框架加载后自动执行 1)概述 HTML执行是按自上而下编译,而<script>一般写在body结束之前.如果在HTML加载的过程中卡住, 比如加载图片等,没有显示出来 ...

  3. Angular页面加载后自动弹窗

    首先在控制器内写好一个弹窗,我用的是ionic的默认提示对话框 // 一个确认对话框 $scope.showConfirm = function() { var confirmPopup = $ion ...

  4. 存储区更新、插入或删除语句影响到了意外的行数(0)。实体在加载后可能被修改或删除。刷新 ObjectStateManager 项。

    在用asp.net MVC3 的MusicStore时候 Edit某个数据项时提示下面的错误: 存储区更新.插入或删除语句影响到了意外的行数(0).实体在加载后可能被修改或删除.刷新 ObjectSt ...

  5. unobtrusive验证,ajax局部加载后验证失效解决方法

    页面加载后运行此代码 $(function() {$.validator.unobtrusive.parse($("form")); }); 原因: 页面加载后unobtrusiv ...

  6. javascript设置网页刷新或者重新加载后滚动条的位置不变

    有个同事说再javascript中你可以做任何你想做的事情,当时觉得不以为然,今天遇到个问题,就是页面重新加载后总是回到页面的顶部,如果客户只想看到他想看到的部分是怎么变化的,这个体验就好了.原本想象 ...

  7. [Asp.net mvc]实体更新异常:存储区更新、插入或删除语句影响到了意外的行数(0)。实体在加载后可能被修改或删除。

    学习asp.net mvc 时在更新实体进行SaveChanges()的时候出现了异常,异常如下: “/”应用程序中的服务器错误. 存储区更新.插入或删除语句影响到了意外的行数(0).实体在加载后可能 ...

  8. 关于NGUI的动态加载后的刷新显示问题,解决办法!!

    http://momowing.diandian.com/post/2012-09-06/40038001275 最近碰NGUI用到它的动态列表功能(ps:就是加东西,删除东西).我这里用的是UIDr ...

  9. uiwebview 加载html时字体变小 加载前或加载后改变字体大小

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 18.0px Menlo; color: #6122ae } p.p2 { margin: 0.0px 0. ...

随机推荐

  1. 终极CURD-4-java8新特性

    目录 1 概述 2 lambda表达式 2.1 lambda重要知识点总结 2.2 java内置函数接口 2.3 方法引用 2.4 构造器引用 2.5 数组引用 2.6 lambda表达式的陷阱 3 ...

  2. MS14-068(CVE-2014-6324)域控提权利用及原理解析

    漏洞利用 0x01 漏洞利用前提 1.域控没有打MS14-068的补丁(KB3011780) 2.拿下一台加入域的计算机 3.有这台域内计算机的域用户密码和Sid 0x02 工具下载 Ms14-068 ...

  3. Mac 安装nginx之后重启、停止、开启等操作

    操作系统:macOs High Sierra 10.13.6 1.我用的homebrew安装的nignx1.15.9,安装完成之后会有下面的提示: 网站根目录在:/usr/local/var/www ...

  4. java基础题月考JSD1908(含答案和解析)

    考试 .container { clear: both; margin: 0 auto; text-align: left; /*width: 1200px;*/ } .container:after ...

  5. CodeForces - 5C(思维+括号匹配)

    题意 https://vjudge.net/problem/CodeForces-5C 给出一个括号序列,求出最长合法子串和它的数量. 合法的定义:这个序列中左右括号匹配. 思路 这个题和普通的括号匹 ...

  6. 设置POP3/SMTP协议 手机绑定邮箱

    例如设置企业邮箱 一.设置POP3/SMTP协议,意思是代收邮件致本地POP3接收邮件服务器:pop.qiye.qq.comSMTP发送邮件服务器:smtp.qiye.qq.com二.设置IMAP/S ...

  7. Gemini.Workflow 双子工作流高级教程:对外API控制引擎:总述

    前言: 双子工作流提供了一套对外的API,用于控制整体系统运转,下面就来看看介绍,其实很简单的. 对外API控制引擎总介: Gemini.Workflow 双子工作流,对外提供的API,都在Gemin ...

  8. 阿里云服务器搭建web项目小结

    前言 最近恰好有时间,自己搞了个云服务器试着搭建了个网站,遇到了一些问题,通过踩坑也涨了一些经验,遂记录一二,与后来者分享. 正文 1.博主用的阿里云服务器,为什么用它呢?一个是恰逢双十一,有优惠:另 ...

  9. Shell命令的执行优先级

    Shell内置命令.外部命令.别名.函数.保留关键字的优先级 在Shell中,有5种可调用的东西:别名(alias).函数(function).shell保留关键字.shell内置命令.外部命令. 如 ...

  10. MySQL的多表联查

    1.内连接        规则:返回两个表的公共记录        语法: -- 语法一 select * from 表1 inner join 表2 on 表1.公共字段=表2.公共字段 -- 语法 ...