如何追踪 WPF 程序中当前获得键盘焦点的元素并显示出来
原文:如何追踪 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 窗口中当前获得键盘焦点的元素。本文介绍监控当前键盘焦点元素的方法,并且提供一个不需要任何调试工具的自己绘制键盘焦点元素的方法。
本文内容
- publishDate_20190628_094929_0800
date_20190629_090754_0800
categories_wpf_dotnet_csharp
position_knowledge_2">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
不过只是拿到这个值并没有多少意义,我们需要:
- 能够实时刷新这个值;
- 能够将这个控件在界面上显示出来。
实时刷新
Keyboard 有路由事件可以监听,得知元素已获得键盘焦点。
Keyboard.AddGotKeyboardFocusHandler(xxx, OnGotFocus);
- 1
这里的 xxx 需要替换成监听键盘焦点的根元素。实际上,对于窗口来说,这个根元素可以唯一确定,就是窗口的根元素。于是我可以写一个辅助方法,用于找到这个窗口的根元素:
// 用于存储当前已经获取过的窗口根元素。
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)
{
// 在这里可以输出或者显示这个获得了键盘焦点的元素。
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
显示
为了显示一个跟踪焦点的控件,我写了一个 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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
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;
}
}
- 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
这样,只要将这个控件放到窗口中,这个控件就会一直跟踪窗口中的当前获得了键盘焦点的元素。当然,为了最好的显示效果,你需要将这个控件放到最顶层。

绘制并实时显示 WPF 程序中当前键盘焦点的元素
如果我们需要监听应用程序中所有窗口中的当前获得键盘焦点的元素怎么办呢?我们需要给所有当前激活的窗口监听 GotKeyboardFocus 事件。
于是,你需要我在另一篇博客中写的方法来监视整个 WPF 应用程序中的所有窗口:
里面有一段对 ApplicationWindowMonitor 类的使用:
var app = Application.Current;
var monitor = new ApplicationWindowMonitor(app);
monitor.ActiveWindowChanged += OnActiveWindowChanged;
void OnActiveWindowChanged(object sender, ActiveWindowEventArgs e)
{
var newWindow = e.NewWindow;
// 一旦有一个新的获得焦点的窗口出现,就可以在这里执行一些代码。
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
于是,我们只需要在 OnActiveWindowChanged 事件中,将我面前面写的控件 KeyboardFocusView 从原来的窗口中移除,然后放到新的窗口中即可监视新的窗口中的键盘焦点。
由于每一次的窗口激活状态的切换都会更新当前激活的窗口,所以,我们可以监听整个 WPF 应用程序中所有窗口中的键盘焦点。
我的博客会首发于 https://blog.walterlv.com/,而 CSDN 会从其中精选发布,但是一旦发布了就很少更新。
如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://walterlv.blog.csdn.net/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。
如何追踪 WPF 程序中当前获得键盘焦点的元素并显示出来的更多相关文章
- 在WPF程序中使用摄像头兼谈如何使用AForge.NET控件(转)
前言: AForge.NET 是用C#写的一个关于计算机视觉和人工智能领域的框架,它包括图像处理.神经网络.遗传算法和机器学习等.在C#程序中使用摄像头,我习惯性使用AForge.NET提供的类库.本 ...
- WPF 程序中启动和关闭外部.exe程序
当需要在WPF程序启动时,启动另一外部程序(.exe程序)时,可以按照下面的例子来: C#后台代码如下: using System; using System.Collections.Generic; ...
- 如何在WPF程序中使用ArcGIS Engine的控件
原文 http://www.gisall.com/html/47/122747-4038.html WPF(Windows Presentation Foundation)是美国微软公司推出.NET ...
- WPF程序中App.Config文件的读与写
WPF程序中的App.Config文件是我们应用程序中经常使用的一种配置文件,System.Configuration.dll文件中提供了大量的读写的配置,所以它是一种高效的程序配置方式,那么今天我就 ...
- 在 WPF 程序中应用 Windows 10 真?亚克力效果
原文:在 WPF 程序中应用 Windows 10 真?亚克力效果 从 Windows 10 (1803) 开始,Win32 应用也可以有 API 来实现原生的亚克力效果了.不过相比于 UWP 来说, ...
- 解决WPF程序中ListBox ItemsSource变化时不重置ScrollBar的问题
解决WPF程序中ListBox ItemsSource变化时不重置ScrollBar的问题 当我们改变ListBox的ItemsSource时,会发现这样一个问题:数据源变化时,虽然控件中的内容会跟着 ...
- 在WPF程序中打开网页:使用代理服务器并可进行JS交互
本项目环境:使用VS2010(C#)编写的WPF程序,通过CefSharp在程序的窗体中打开网页.需要能够实现网页后台JS代码中调用的方法,从网页接收数据,并能返回数据给网页.运行程序的电脑不允许上网 ...
- WPF程序中的弱事件模式
在C#中,得益于强大的GC机制,使得我们开发程序变得非常简单,很多时候我们只需要管使用,而并不需要关心什么时候释放资源.但是,GC有的时并不是按照我们所期望的方式工作. 例如,我想实现一个在窗口的标题 ...
- WPF设置控件获取键盘焦点时的样式FocusVisualStyle
控件获取焦点除了用鼠标外,可以通过键盘来获取,比如Tab键或者方向键等,需要设置控件获取键盘焦点时的样式,可以通过设置FrameworkElemnt.FocusVisualStyle属性, 因为几乎所 ...
随机推荐
- MVC WebApi Swagger帮助文档 接口用法
1.WebApi在解决方案Apps.WebApi中 2.将Apps.WebApi设置为启动项目之后,可以直接浏览到Api的帮助文档,并直接进行调试 3.登录接口 4.登录获取的token来访问其他接口 ...
- pyqt(day1)
参考代码地址:https://github.com/cxinping/Pyqt5 pyqt在线帮助文档:https://www.riverbankcomputing.com/static/Docs/P ...
- sql注入用<>绕过被过滤的select ——百度杯9月第二场SQL
题目提示SQL注入,打开源代码发现<!--SELECT * FROM info WHERE id=1--> 尝试union select 1,2,3提示inj code! 经过多次尝试之后 ...
- [技术博客] 利用Vagrant+virtualbox在windows下进行linux开发
目录 加速box安装的方法 root账户登录 换源教程 安装rvm 访问rails server RubyMine连接虚拟机上的解释器 作者:庄廓然 在windows下进行linux开发:利用Vagr ...
- Gevent简明教程
Gevent简明教程 发表于 2015-11-28 | 分类于 技术| | 阅读次数 5159 前述 进程 线程 协程 异步 并发编程(不是并行)目前有四种方式:多进程.多线程.协程和异步. 多 ...
- android双进程守护,让程序崩溃后一定可以重启
由于我们做的是机器人上的软件,而机器人是24小时不间断服务的,这就要求我们的软件不能退出到系统桌面.当然最好是能够做到程序能够不卡顿,不崩溃,自己不退出.由于我们引用了很多第三方的开发包,也不能保证他 ...
- Twitter雪花算法SnowFlake算法的java实现
https://juejin.im/post/5c75132f51882562276c5065 package javaDemo; /** * twitter的snowflake算法 -- java实 ...
- ES6 - 对象扩展(增强字面量)
/** * 对象的扩展 * * 增强对象字面量 * * 解决问题:缩减代码 */ { /** * 1.属性简表示法 * 变量foo直接写在大括号里面.这时,属性名就是变量名, 属性值就是变量值 */ ...
- Java13新特性 -- 文本块
在JDK 12中引入了Raw String Literals特性,但在发布之前就放弃了.这个JEP与引入多行字符串文字(text block) 在意义上是类似的. 这条新特性跟 Kotlin 里的文本 ...
- 【SpringBoot】SpringBoot 国际化(七)
本周介绍SpringBoot项目的国际化是如何处理的,阅读本章前请阅读[SpringBoot]SpringBoot与Thymeleaf模版(六)的相关内容 国际化原理 1.在Spring中有国际化Lo ...
