Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >如何追踪 WPF 程序中当前获得键盘焦点的元素并显示出来

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

作者头像
walterlv
发布于 2023-10-22 02:20:41
发布于 2023-10-22 02:20:41
6590
举报

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


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

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

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

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

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

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

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

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

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

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

1

var focusedElement = Keyboard.FocusedElement;

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

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

实时刷新

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

1

Keyboard.AddGotKeyboardFocusHandler(xxx, OnGotFocus);

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

1 2 3 4 5 6 7 8 9

// 用于存储当前已经获取过的窗口根元素。 private FrameworkElement _root; // 获取当前窗口的根元素。 private FrameworkElement Root => _root ?? (_root = FindRootVisual(this)); // 一个辅助方法,用于根据某个元素为起点查找当前窗口的根元素。 private static FrameworkElement FindRootVisual(FrameworkElement source) => (FrameworkElement)((HwndSource)PresentationSource.FromVisual(source)).RootVisual;

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

1 2 3 4 5 6 7 8 9

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

显示

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

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

<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>

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69

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 应用程序中的所有窗口:

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

1 2 3 4 5 6 7 8 9

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/post/how-to-track-wpf-focused-element.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-06-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
从JVM层面来解释i++和++i的真正区别
但是这简单的回答并不能入吸引面试官的眼球,如果用java字节码指令分析则效果完全不同。
肉眼品世界
2021/07/13
4110
从JVM层面来解释i++和++i的真正区别
短文:用字节码解析i++和++i的区别
是不是很无聊,相信很多人会回答,i++是先赋值再+1,++i是先+1再赋值。确实是这样,但是我总是想追根溯源,如何解释这个原则?
源码之路
2021/02/02
8370
如何从字节码角度分析Java问题
很简单的两行代码,如果是你遇到这样的问题,你会怎样去把问题解释清楚?是利用Java运算符顺序将式子拆解,然后一步步运算,还是其他什么办法?
叫我阿柒啊
2022/05/09
6010
如何从字节码角度分析Java问题
JVM字节码与Java代码层调优
我们都知道,Java源代码不会像C/C++那样直接被编译为机器码,而是被编译成字节码,这造就了Java可以跨平台的特性。JVM实际执行的也是编译后的字节码,所以想要在Java代码层进行调优,就得对字节码有一定的了解。
端碗吹水
2020/09/23
4710
JVM字节码与Java代码层调优
敖丙字节一面:能聊聊字节码么?
上一篇《你能和我聊聊Class文件么》中,我们对Class文件的各个部分做了简单的介绍,当时留了一个很重要的部分没讲,不是敖丙不想讲啊,而是这一部分实在太重要了,不独立成篇好好zhejinrong 讲讲都对不起詹姆斯·高斯林
敖丙
2022/04/19
3420
敖丙字节一面:能聊聊字节码么?
从字节码层面分析==比较integer和int
可以看出先把两个1存到操作栈中,然后把操作栈中的1存到局部变量表中,最后在比较的时候再把两个1从局部变量表中放到操作数栈中执行if_icmpne逻辑判断来进行比较。其中astore和istore是精髓,astore代表把integer自动拆箱用来存储到局部变量表中。 当数值改为127以上时,唯一改变的字节码指令是iconst->sipush,这是因为: 取值-128~127采用bipush指令, 取值-32768~32767采用sipush指令 取值-2147483648~2147483647采用 ldc 指令。
gzq大数据
2021/09/23
3500
从JAVA字节码到方法运行
很简单的两行代码,如果是你遇到这样的问题,你会怎样去把问题解释清楚?是利用Java运算符顺序将式子拆解,然后一步步运算,还是其他什么办法?在思索一会儿之后,决定还是通过字节码指令来看看这两行代码是怎么运行的。
叫我阿柒啊
2022/05/09
5190
从JAVA字节码到方法运行
jvm之类文件详解(四)
Class 文件是一组以 8 位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在 Class 文件之中,中间没有添加任何 分隔符,这使得整个 Class 文件中存储的内容几乎全部是程序运行的必要数据,没有空隙存在。 当遇到需要占用 8 位字节以上空间的数据项时,则会按照高位在前(Big-Endian)的方式分割成若干个 8 位字节进行存储。 Class 文件只有两种数据类型:无符号数和表 链接:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html
周杰伦本人
2022/10/25
2080
jvm之类文件详解(四)
一个求水仙花数的类的字节码分析
使用javap得到字节码: javap -verbose com.dhb.geektimestudy.kimmking.week1.Hello
冬天里的懒猫
2021/08/10
2790
异常原理 | 优雅,永不过时
Java 虚拟机里面的异常使用 Throwable 或其子类的实例来表示,抛异常的本质实际上是程序控制权的一种即时的、非局部(Nonlocal)的转换——从异常抛出的地方转换至处理异常的地方。绝大多数的异常的产生都是由于当前线程执行的某个操作所导致的,这种可以称为是同步的异常。与之相对的,异步异常是指在程序的其他任意地方进行的动作而导致的异常。 Java 虚拟机中异常的出现总是由下面三种原因之一导致的:
不惑
2023/12/13
2580
异常原理 | 优雅,永不过时
[JVM] JVM自动内存管理机制(一)
文本主要就JVM结构和字节码文件,进行分析来展开JVM的学习,后续系列文章会从JVM的多个方面的进行知识总结。
架构探险之道
2019/09/09
5350
[JVM] JVM自动内存管理机制(一)
JVM内存与垃圾回收篇第5章虚拟机栈
https://docs.oracle.com/javase/specs/jvms/se8/html/index.html
yuanshuai
2022/08/22
4490
JVM内存与垃圾回收篇第5章虚拟机栈
程序员进阶系列:年少不懂爱家家,懂了已是猿中人。
时隔多年,回想起那个面试场景,忍不住要感叹:年少不懂i++(爱家家),如今懂了却已是老码农(双鬓白)。
一猿小讲
2020/09/01
3280
程序员进阶系列:年少不懂爱家家,懂了已是猿中人。
1、引言
栈帧对应一个线程的一个方法的内容,用于方法的执行,包括方法执行过程中的变量的临时状态。同时栈帧也执行动态链接,方法的返回值以及分发异常。栈帧被包含在JVM栈中。每一个栈帧包括:
文彬
2022/06/06
3900
1、引言
JVM学习笔记——类加载和字节码技术篇
在本系列内容中我们会对JVM做一个系统的学习,本片将会介绍JVM的类加载和字节码技术部分
秋落雨微凉
2022/11/16
5630
JVM学习笔记——类加载和字节码技术篇
001. JAVA程序运行原理分析
1. 先来看看JVM运行时数据区的结构 线程独占: 每个线程都有它独立的空间,随线程生命周期而创建和销毁。 线程共享: 所有线程能访问这块内存数据,随虚拟机GC 而创建和销毁。 方法区 JVM 用来存
山海散人
2021/03/03
4060
001. JAVA程序运行原理分析
深入理解 JVM 之——字节码指令与执行引擎
对于 C 语言从程序到运行需要经过编译的过程,只有经历了编译后,我们所编写的代码才能够翻译为机器可以直接运行的二进制代码,并且在不同的操作系统下,我们的代码都需要进行一次编译之后才能运行。
浪漫主义狗
2023/09/07
5650
深入理解 JVM 之——字节码指令与执行引擎
初识JVM指令执行流程
摘要: 记录下学习JVM指令执行流程的理解 正文: 初识JVM指令执行流程 /** * 0: aload_0 * 1: invokespecial #1 // Method java/lang/Object."<init>":()V * 4: return * * @author liugang * @since 2018-04-28 */ public class Example1 { /** * 为主方法创建一个frame并将其推入线程
itliusir
2018/05/21
5020
面试官:解释一下Java字节码文件中的JVM指令
Java 之所以流行,一个很重要的原因就是它的跨平台特性,Compile Once, Run Anywhere,编译一次,到处运行。即 Java 源码只需要编译成字节码文件,之后就可以在不同的操作系统(Windows、Mac、Linux)运行,准确讲是运行在操作系统上的 JVM 中。
南风
2019/11/22
7450
字节码文件 Krains 2020-08-04
得到反编译的字节码文件,这些信息都会通过classloader加载到方法区当中,但在运行时方法区中还包含了加载该class文件的classloader信息,当前只是反编译,没有经过classloader。
Krains
2020/08/05
2280
推荐阅读
相关推荐
从JVM层面来解释i++和++i的真正区别
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档