当前位置: 首页 > news >正文

如何追踪 WPF 程序中当前获得键盘焦点的元素并显示出来


title: “如何追踪 WPF 程序中当前获得键盘焦点的元素并显示出来”
publishDate: 2019-06-28 09:49:29 +0800
date: 2019-06-29 09:07:54 +0800
categories: wpf dotnet csharp
position: knowledge

我们有很多的调试工具可以帮助我们查看 WPF 窗口中当前获得键盘焦点的元素。本文介绍监控当前键盘焦点元素的方法,并且提供一个不需要任何调试工具的自己绘制键盘焦点元素的方法。


本文内容

    • title: "如何追踪 WPF 程序中当前获得键盘焦点的元素并显示出来" publishDate: 2019-06-28 09:49:29 +0800 date: 2019-06-29 09:07:54 +0800 categories: wpf dotnet csharp position: knowledge
    • 使用调试工具查看当前获得键盘焦点的元素
    • 使用代码查看当前获得键盘焦点的元素
      • 实时刷新
      • 显示
    • 绘制并实时显示 WPF 程序中当前键盘焦点的元素

使用调试工具查看当前获得键盘焦点的元素

Visual Studio 带有实时可视化树的功能,使用此功能调试 WPF 程序的 UI 非常方便。

打开实时可视化树

在打开实时可视化树后,我们可以略微认识一下这里的几个常用按钮:

[外链图片转存失败(img-mi7VNLBt-1567148281031)(https://blog.walterlv.com/static/posts/2019-06-28-09-03-11.png)]

这里,我们需要打开两个按钮:

  • 为当前选中的元素显示外框
  • 追踪具有焦点的元素

这样,只要你的应用程序当前获得焦点的元素发生了变化,就会有一个表示这个元素所在位置和边距的叠加层显示在窗口之上。

实时可视化树中的焦点追踪

你可能已经注意到了,Visual Studio 附带的这一叠加层会导致鼠标无法穿透操作真正具有焦点的元素。这显然不能让这一功能一直打开使用,这是非常不方便的。

使用代码查看当前获得键盘焦点的元素

我们打算在代码中编写追踪焦点的逻辑。这可以规避 Visual Studio 中叠加层中的一些问题,同时还可以在任何环境下使用,而不用担心有没有装 Visual Studio。

获取当前获得键盘焦点的元素:

var focusedElement = Keyboard.FocusedElement;

不过只是拿到这个值并没有多少意义,我们需要:

  1. 能够实时刷新这个值;
  2. 能够将这个控件在界面上显示出来。

实时刷新

Keyboard 有路由事件可以监听,得知元素已获得键盘焦点。

Keyboard.AddGotKeyboardFocusHandler(xxx, OnGotFocus);

这里的 xxx 需要替换成监听键盘焦点的根元素。实际上,对于窗口来说,这个根元素可以唯一确定,就是窗口的根元素。于是我可以写一个辅助方法,用于找到这个窗口的根元素:

// 用于存储当前已经获取过的窗口根元素。
private FrameworkElement _root;

// 获取当前窗口的根元素。
private FrameworkElement Root => _root ?? (_root = FindRootVisual(this));

// 一个辅助方法,用于根据某个元素为起点查找当前窗口的根元素。
private static FrameworkElement FindRootVisual(FrameworkElement source) =>
    (FrameworkElement)((HwndSource)PresentationSource.FromVisual(source)).RootVisual;

于是,监听键盘焦点的代码就可以变成:

Keyboard.AddGotKeyboardFocusHandler(Root, OnGotFocus);

void OnGotFocus(object sender, KeyboardFocusChangedEventArgs e)
{
    if (e.NewFocus is FrameworkElement fe)
    {
        // 在这里可以输出或者显示这个获得了键盘焦点的元素。
    }
}

显示

为了显示一个跟踪焦点的控件,我写了一个 UserControl,里面的主要代码是:

<Canvas IsHitTestVisible="False">
    <Border x:Name="FocusBorder" BorderBrush="#80159f5c" BorderThickness="4"
            HorizontalAlignment="Left" VerticalAlignment="Top"
            IsHitTestVisible="False" SnapsToDevicePixels="True">
        <Border x:Name="OffsetBorder" Background="#80159f5c"
                Margin="-200 -4 -200 -4" Padding="12 0"
                HorizontalAlignment="Center" VerticalAlignment="Bottom"
                SnapsToDevicePixels="True">
            <Border.RenderTransform>
                <TranslateTransform x:Name="OffsetTransform" Y="16" />
            </Border.RenderTransform>
            <TextBlock x:Name="FocusDescriptionTextBlock" Foreground="White" HorizontalAlignment="Center" />
        </Border>
    </Border>
</Canvas>
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Threading;

namespace Walterlv.Windows
{
    public partial class KeyboardFocusView : UserControl
    {
        public KeyboardFocusView()
        {
            InitializeComponent();
            Loaded += OnLoaded;
            Unloaded += OnUnloaded;
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            if (Keyboard.FocusedElement is FrameworkElement fe)
            {
                SetFocusVisual(fe);
            }
            Keyboard.AddGotKeyboardFocusHandler(Root, OnGotFocus);
        }

        private void OnUnloaded(object sender, RoutedEventArgs e)
        {
            Keyboard.RemoveGotKeyboardFocusHandler(Root, OnGotFocus);
            _root = null;
        }

        private void OnGotFocus(object sender, KeyboardFocusChangedEventArgs e)
        {
            if (e.NewFocus is FrameworkElement fe)
            {
                SetFocusVisual(fe);
            }
        }

        private void SetFocusVisual(FrameworkElement fe)
        {
            var topLeft = fe.TranslatePoint(new Point(), Root);
            var bottomRight = fe.TranslatePoint(new Point(fe.ActualWidth, fe.ActualHeight), Root);
            var isOnTop = topLeft.Y < 16;
            var isOnBottom = bottomRight.Y > Root.ActualHeight - 16;

            var bounds = new Rect(topLeft, bottomRight);
            Canvas.SetLeft(FocusBorder, bounds.X);
            Canvas.SetTop(FocusBorder, bounds.Y);
            FocusBorder.Width = bounds.Width;
            FocusBorder.Height = bounds.Height;

            FocusDescriptionTextBlock.Text = string.IsNullOrWhiteSpace(fe.Name)
                ? $"{fe.GetType().Name}"
                : $"{fe.Name}({fe.GetType().Name})";
        }

        private FrameworkElement _root;

        private FrameworkElement Root => _root ?? (_root = FindRootVisual(this));

        private static FrameworkElement FindRootVisual(FrameworkElement source) =>
            (FrameworkElement)((HwndSource)PresentationSource.FromVisual(source)).RootVisual;
    }
}

这样,只要将这个控件放到窗口中,这个控件就会一直跟踪窗口中的当前获得了键盘焦点的元素。当然,为了最好的显示效果,你需要将这个控件放到最顶层。

实时可视化树中的焦点追踪

绘制并实时显示 WPF 程序中当前键盘焦点的元素

如果我们需要监听应用程序中所有窗口中的当前获得键盘焦点的元素怎么办呢?我们需要给所有当前激活的窗口监听 GotKeyboardFocus 事件。

于是,你需要我在另一篇博客中写的方法来监视整个 WPF 应用程序中的所有窗口:

  • 如何监视 WPF 中的所有窗口,在所有窗口中订阅事件或者附加 UI

里面有一段对 ApplicationWindowMonitor 类的使用:

var app = Application.Current;
var monitor = new ApplicationWindowMonitor(app);
monitor.ActiveWindowChanged += OnActiveWindowChanged;

void OnActiveWindowChanged(object sender, ActiveWindowEventArgs e)
{
    var newWindow = e.NewWindow;
    // 一旦有一个新的获得焦点的窗口出现,就可以在这里执行一些代码。
}

于是,我们只需要在 OnActiveWindowChanged 事件中,将我面前面写的控件 KeyboardFocusView 从原来的窗口中移除,然后放到新的窗口中即可监视新的窗口中的键盘焦点。

由于每一次的窗口激活状态的切换都会更新当前激活的窗口,所以,我们可以监听整个 WPF 应用程序中所有窗口中的键盘焦点。


我的博客会首发于 https://blog.walterlv.com/,而 CSDN 会从其中精选发布,但是一旦发布了就很少更新。

如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。

知识共享许可协议

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://walterlv.blog.csdn.net/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。

相关文章:

  • 使用 Visual Studio 编译时,让错误一开始发生时就停止编译(以便及早排查编译错误节省时间)
  • .NET / MSBuild 扩展编译时什么时候用 BeforeTargets / AfterTargets 什么时候用 DependsOnTargets?
  • 在项目文件 / MSBuild / NuGet 包中编写扩展编译的时候,正确使用 props 文件和 targets 文件
  • .NET Framework 的 bug?try-catch-when 中如果 when 语句抛出异常,程序将彻底崩溃
  • .NET/MSBuild 中的发布路径在哪里呢?如何在扩展编译的时候修改发布路径中的文件呢?
  • 如何给 Windows Terminal 增加一个新的终端(以 Bash 为例)
  • 在 Visual Studio 中设置当发生某个特定异常或所有异常时中断
  • .NET/C# 中设置当发生某个特定异常时进入断点(不借助 Visual Studio 的纯代码实现)
  • 如何在 Windows 10 中安装 WSL2 的 Linux 子系统
  • 如何安装和准备 Visual Studio 扩展/插件开发环境
  • 基于 Roslyn 同时为 Visual Studio 插件和 NuGet 包开发 .NET/C# 源代码分析器 Analyzer 和修改器 CodeFixProvider
  • 软件界面中一些易混淆/易用错的界面文案,以及一些约定俗成的文案约定
  • WPF 的 VisualBrush 只刷新显示的视觉效果,不刷新布局范围
  • .NET/C# 使用 #if 和 Conditional 特性来按条件编译代码的不同原理和适用场景
  • 使用 Roslyn 分析代码注释,给 TODO 类型的注释添加负责人、截止日期和 issue 链接跟踪
  • 【Leetcode】104. 二叉树的最大深度
  • 345-反转字符串中的元音字母
  • CAP理论的例子讲解
  • JavaScript中的对象个人分享
  • Linux后台研发超实用命令总结
  • rc-form之最单纯情况
  • vue-cli在webpack的配置文件探究
  • 海量大数据大屏分析展示一步到位:DataWorks数据服务+MaxCompute Lightning对接DataV最佳实践...
  • 基于游标的分页接口实现
  • 技术:超级实用的电脑小技巧
  • 前端每日实战:61# 视频演示如何用纯 CSS 创作一只咖啡壶
  • 前嗅ForeSpider采集配置界面介绍
  • 如何进阶一名有竞争力的程序员?
  • 小试R空间处理新库sf
  • 中国人寿如何基于容器搭建金融PaaS云平台
  • Mac 上flink的安装与启动
  • ​​​​​​​GitLab 之 GitLab-Runner 安装,配置与问题汇总
  • #经典论文 异质山坡的物理模型 2 有效导水率
  • $con= MySQL有关填空题_2015年计算机二级考试《MySQL》提高练习题(10)
  • (floyd+补集) poj 3275
  • (二十三)Flask之高频面试点
  • (利用IDEA+Maven)定制属于自己的jar包
  • (论文阅读40-45)图像描述1
  • (学习日记)2024.02.29:UCOSIII第二节
  • (一)python发送HTTP 请求的两种方式(get和post )
  • (原創) 如何讓IE7按第二次Ctrl + Tab時,回到原來的索引標籤? (Web) (IE) (OS) (Windows)...
  • (转)eclipse内存溢出设置 -Xms212m -Xmx804m -XX:PermSize=250M -XX:MaxPermSize=356m
  • (转)es进行聚合操作时提示Fielddata is disabled on text fields by default
  • (转)清华学霸演讲稿:永远不要说你已经尽力了
  • (转)四层和七层负载均衡的区别
  • (转载)OpenStack Hacker养成指南
  • (状压dp)uva 10817 Headmaster's Headache
  • .[hudsonL@cock.li].mkp勒索病毒数据怎么处理|数据解密恢复
  • .Net接口调试与案例
  • .Net中ListT 泛型转成DataTable、DataSet
  • @FeignClient注解,fallback和fallbackFactory
  • [20160902]rm -rf的惨案.txt
  • [2017][note]基于空间交叉相位调制的两个连续波在few layer铋Bi中的全光switch——
  • [20171113]修改表结构删除列相关问题4.txt
  • [23] 4K4D: Real-Time 4D View Synthesis at 4K Resolution