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

焦点概述 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

原文地址:https://www.cnblogs.com/dino623/p/AutoFocus.html

时间: 2024-08-26 01:56:09

[WPF自定义控件库] 让Form在加载后自动获得焦点的相关文章

springboot 容器加载后自动监听 获取access_token

问题来源: 因为在项目中需要获取微信的access_token ,access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token.开发者需要进行妥善保存.access_token的存储至少要保留512个字符空间.access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效. 所以需要一个线程,来定时获取和更新access_token 一 新建 TokenThread 线程 package com.hm

[WPF自定义控件库]为Form和自定义Window添加FunctionBar

1. 前言 我常常看到同一个应用程序中的表单的按钮----也就是"确定"."取消"那两个按钮----实现得千奇百怪,其实只要使用统一的Style起码就可以统一按钮的大小,而我喜欢更进一步将"确定"."取消"或其它按钮封装进一个自定义控件里. 这篇文章介绍了另一种ItemsControl的实现方式,并使用它为表单及自定义Window添加常用的按钮及其它功能. 2. 为Form添加FunctionBar 本来打算派生自ToolBa

WPF防止界面卡死并显示加载中效果

原文:WPF防止界面卡死并显示加载中效果 网上貌似没有完整的WPF正在加载的例子,所以自己写了一个,希望能帮到有需要的同学 前台: <Window x:Class="WpfApplication1.Loading" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml

[WPF自定义控件库]使用WindowChrome自定义RibbonWindow

原文:[WPF自定义控件库]使用WindowChrome自定义RibbonWindow 1. 为什么要自定义RibbonWindow 自定义Window有可能是设计或功能上的要求,可以是非必要的,而自定义RibbonWindow则不一样: 如果程序使用了自定义样式的Window,为了统一外观需要把RibbonWindow一起修改样式. 为了解决RibbonWindow的BUG. 如上图所示,在Windows 10 上运行打开RibbonWindow,可以看到标题栏的内容(包括分隔符)没有居中对齐

JavaScript之jQuery-4 jQuery事件(页面加载后执行、事件处理、事件冒泡、事件对象、模拟操作)

一.jQuery 页面加载后执行 代码执行的时机选择 - $(document).ready()方法和 window.onload事件具有相似的功能,但是在执行时机方面是有区别的 - window.onload 事件是在网页中所有的元素(包括元素的所有关联文件)完全加载到浏览器后才执行 - $(document).ready()方法注册的事件处理程序,在DOM完全加载后就可以调用 - 一般来讲, $(document).ready()的执行要优于window.onload事件 - 需要注意的是,

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

页面加载后运行此代码 $(function() {$.validator.unobtrusive.parse($("form")); }); 原因: 页面加载后unobtrusive已对各标签的验证状态进行保存,页面局部加载后并不会导致unobtrusive重新对页面的控件进行验证并保存前状态,导致新页面无法进行验证. 原理: 注意看jquery.validate.unobtrusive源码最后一段: $(function () { $jQval.unobtrusive.parse(d

页面加载后累加,自加1&amp;&amp;判断数字是否为两位数

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-

每日一题_JavaScript.两种方式实现网页加载后onload绑定多个函数?

具体需求: 1. 为网页加载后触发的onload事件绑定多个执行函数 实现思路: 1. 可直接给onload绑定一个匿名函数,匿名函数内部调用多个函数 2. 可自定义个函数,首先保存之前window.onload的值,然后判断window.onload的类型是否为function,如果不是就让window.onload的值设置为自定义的函数,否则就先执行window.onload之前绑定的函数,然后在执行自定义的函数 具体代码: <!DOCTYPE html> <html>    

关于服务重新加载后无效问题的总结

在工作中碰到一个问题,从运维管理平台向交易平台发起重新加载服务请求,服务重新加载后再去做交易,而刚修改的限额.限次等配置信息没有生效. 以下是该问题相关情况: 1.本地代码运行没有该问题,部署到服务器上就有问题. 2.本地代码启动过程中服务只起了一次,而服务器上启动时起了两次: 3.远程调试服务器上代码,发现重新加载服务时,更新的限额限次配置和重新进行交易时使用的限额.限次配置不是同一个变量(变量的id不一致). 4.限额限次配置对应的存储变量使用的是volatile修饰符修饰的.从而实现多线程