如何监视 WPF 中的所有窗口,在所有窗口中订阅事件或者附加 UI
原文:如何监视 WPF 中的所有窗口,在所有窗口中订阅事件或者附加 UI
由于 WPF 路由事件(主要是隧道和冒泡)的存在,我们很容易能够通过只监听窗口中的某些事件使得整个窗口中所有控件发生的事件都被监听到。然而,如果我们希望监听的是整个应用程序中所有的事件呢?路由事件的路由可并不会跨越窗口边界呀?
本文将介绍我编写的应用程序窗口监视器,来监听整个应用程序中所有窗口中的路由事件。这样的方法可以用来无时无刻监视 WPF 程序的各种状态。
其实问题依旧摆在那里,因为我们依然无法让路由事件跨越窗口边界。更麻烦的是,我们甚至不知道应用程序有哪些窗口,这些窗口都是什么时机显示出来的。
Application 类中有一个属性 Windows,这是一个 WindowCollection 类型的属性,可以用来获取当前已经被 Application 类管理的所有的窗口的集合。当然 Application 类内部还有一个属性 NonAppWindowsInternal 用来管理与此 Application 没有逻辑关系的窗口集合。
于是,我们只需要遍历 Windows 集合便可以获得应用程序中的所有窗口,然后对每一个窗口监听需要的路由事件。
var app = Application.Current;
foreach (Window window in app.Windows)
{
// 在这里监听窗口中的事件。
}
- 1
- 2
- 3
- 4
- 5
等等!这种操作意味着将来新打开的窗口是不会被监听到事件的。
我们有没有方法拿到新窗口的显示事件呢?遗憾的是——并不行。
但是,我们有一些变相的处理思路。比如,由于 Windows 系统的特性,整个用户空间内,统一时刻只能有一个窗口能处于激活状态。我们可以利用当前窗口的激活与非激活的切换时机再去寻找新的窗口。
于是,一开始的时候,我们可以监听一些窗口的激活事件。如果执行这段初始化代码的时候没有任何窗口是激活的状态,那么就监听所有窗口的激活事件;如果有一个窗口是激活的,那么就监听这个窗口的取消激活事件。
private void InitializeActivation()
{
var app = Application.Current;
var availableWindows = app.Windows.ToList();
var activeWindow = availableWindows.FirstOrDefault(x => x.IsActive);
if (activeWindow == null)
{
foreach (var window in availableWindows)
{
window.Activated -= Window_Activated;
window.Activated += Window_Activated;
}
}
else
{
activeWindow.Deactivated -= Window_Deactivated;
activeWindow.Deactivated += Window_Deactivated;
UpdateActiveWindow(activeWindow);
}
}
private void UpdateActiveWindow(Window window)
{
// 当前激活的窗口已经发生了改变,可以在这里为新的窗口做一些事情了。
}
- 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
在 Window_Activated 和 Window_Deactivated 事件中,我们主要也是在做初始化。
现在思路基本上全部清晰了,于是我将我写的 ApplicationWindowMonitor 类的全部源码贴出来。
using System;
using System.Linq;
using System.Windows;
using System.Windows.Threading;
namespace Walterlv.Windows
{
public sealed class ApplicationWindowMonitor
{
private readonly Application _app;
private readonly Predicate<Window> _windowFilter;
private Window _lastActiveWindow;
public ApplicationWindowMonitor(Application app, Predicate<Window> windowFilter = null)
{
_app = app ?? throw new ArgumentNullException(nameof(app));
_windowFilter = windowFilter;
_app.Dispatcher.InvokeAsync(InitializeActivation, DispatcherPriority.Send);
}
private void InitializeActivation()
{
var availableWindows = _app.Windows.OfType<Window>().Where(FilterWindow).ToList();
var activeWindow = availableWindows.FirstOrDefault(x => x.IsActive);
if (activeWindow == null)
{
foreach (var window in availableWindows)
{
window.Activated -= Window_Activated;
window.Activated += Window_Activated;
}
}
else
{
activeWindow.Deactivated -= Window_Deactivated;
activeWindow.Deactivated += Window_Deactivated;
UpdateActiveWindow(activeWindow);
}
}
private void Window_Activated(object sender, EventArgs e)
{
var window = (Window) sender;
window.Activated -= Window_Activated;
window.Deactivated -= Window_Deactivated;
window.Deactivated += Window_Deactivated;
UpdateActiveWindow(window);
}
private void Window_Deactivated(object sender, EventArgs e)
{
var availableWindows = _app.Windows.OfType<Window>().Where(FilterWindow).ToList();
foreach (var window in availableWindows)
{
window.Deactivated -= Window_Deactivated;
window.Activated -= Window_Activated;
window.Activated += Window_Activated;
}
}
private void UpdateActiveWindow(Window window)
{
if (!Equals(window, _lastActiveWindow))
{
try
{
OnActiveWindowChanged(_lastActiveWindow, window);
}
finally
{
_lastActiveWindow = window;
}
}
}
private bool FilterWindow(Window window) => _windowFilter == null || _windowFilter(window);
public event EventHandler<ActiveWindowEventArgs> ActiveWindowChanged;
private void OnActiveWindowChanged(Window oldWindow, Window newWindow)
{
ActiveWindowChanged?.Invoke(this, new ActiveWindowEventArgs(oldWindow, newWindow));
}
}
}
- 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
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
使用方法是:
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
另外,我在 ApplicationWindowMonitor 的构造函数中加入了一个过滤窗口的委托。比如你可以让窗口的监听只对主要的几个窗口生效,而对一些信息提示窗口忽略等等。
我的博客会首发于 https://blog.walterlv.com/,而 CSDN 会从其中精选发布,但是一旦发布了就很少更新。
如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://walterlv.blog.csdn.net/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。
如何监视 WPF 中的所有窗口,在所有窗口中订阅事件或者附加 UI的更多相关文章
- WPF中任务栏只显示主窗口
我们在用WPF开发的时候,常常会遇到在主窗口打开的情况下,去显示子窗口,而此时任务栏同时显示主窗口与子窗口.这样看起来很不美观.所以在弹出子窗口之前,设置它的几个相应属性,便不会出现这种问题了. // ...
- 【WPF】监听WPF的WebBrowser控件弹出新窗口的事件
原文:[WPF]监听WPF的WebBrowser控件弹出新窗口的事件 WPF中自带一个WebBrowser控件,当我们使用它打开一个网页,例如百度,然后点击它其中的链接时,如果这个链接是会弹出一个新窗 ...
- 【WPF】在新线程上打开窗口
当WPF应用程序运行时,默认会创建一个UI主线程(因为至少需要一个),并在该UI线程上启动消息循环.直到消息循环结束,应用程序就随即退出.那么,问题就来了,能不能创建新线程,然后在新线程上打开一个新窗 ...
- WPF中的Visual Tree和Logical Tree与路由事件
1.Visual Tree和Logical TreeLogical Tree:逻辑树,WPF中用户界面有一个对象树构建而成,这棵树叫做逻辑树,元素的声明分层结构形成了所谓的逻辑树!!Visual Tr ...
- WPF编程,使用WindowChrome实现自定义窗口功能的一种方法。
原文:WPF编程,使用WindowChrome实现自定义窗口功能的一种方法. 版权声明:我不生产代码,我只是代码的搬运工. https://blog.csdn.net/qq_43307934/arti ...
- WPF 学习笔记-设置属性使窗口不可改变大小
原文:WPF 学习笔记-设置属性使窗口不可改变大小 调整Windows下的ResizeMode属性: ResizeMode = NoResize Resize属性是控制Windows是否可以改变大小, ...
- vim中project多标签和多窗口的使用
1.打开多个窗口 打开多个窗口的命令以下几个: 横向切割窗口 :new+窗口名(保存后就是文件名) :split+窗口名,也可以简写为:sp+窗口名 纵向切割窗口名 :vsplit+窗口名,也可以简写 ...
- 第十三篇:在SOUI中使用有窗口句柄的子窗口
前言: 无论一个DirectUI系统提供的DUI控件多么丰富,总会有些情况下用户需要在DUI窗口上放置有窗口句柄的子窗口. 为了和无窗口句柄的子窗口相区别,这里将有窗口句柄的子窗口称之为真窗口. 每一 ...
- HTML中IFrame父窗口与子窗口相互操作
一.Iframe篇 //&&&&&&&&&&&&&&&&&&am ...
随机推荐
- 一大波 Facebook Libra 技术文章来袭
由于 Libra 具有真正的稳定的可编程货币的特性, Libra 或许可以带来又一波平台红利. 上周我们(深入浅出区块链技术社区)发起了 Libra 技术文档的翻译计划,到这周文档已经翻译了一半.欢迎 ...
- JS简单获取当前日期时间的方法(yyyy-MM-dd hh:mm:ss)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xht ...
- [技术博客] win10下vagrant+centos7 rails虚拟开发机配置流程
由于少昂早年已经在此踩过坑了,因此在这里,我们现在直接贴上他早年的博客链接:https://www.cnblogs.com/HansBug/p/7403306.html
- HTTP协议之multipart/form-data
HTTP协议之multipart/form-data REST API可以用multipart/form-data,来上传文件. 在最初的 http 协议中,没有上传文件方面的功能. rfc1867为 ...
- Linux平台Boost 1.6.7的编译方法
boost库下载地址:https://dl.bintray.com/boostorg/release/ 编译: 1. 获得bjam (1) # cd /usr/src/boost_1_67_0 (2) ...
- IntelliJ IDEA-设置字体大小
setting 设置字体font大小,点击apply-ok
- PHP系列 | Thinkphp3.2 上传七牛 bad token 问题 [ layui.upload 图片/文件上传]
前端代码 <div class="logo_out" id="upload-logo"></div> JS代码 /** * 上传图片 * ...
- js判断字符串是否以指定字符串开头或是否包含指定字符串
1. 用js判断一个字符串是否是以某个子字符串开头如:ssss001是否以ssss开头, 可以这样做: 1 2 3 4 5 6 var fdStart = strCode.indexOf(" ...
- Mariadb 10.3.17 中启用binlog
先检查是否开启了binlogSHOW VARIABLES LIKE 'log_bin';如果提示:+---------------+-------+| Variable_name | Value |+ ...
- Content-Type四种常见取值
一.application/x-www-form-urlencoded 最常见 POST 提交数据的方式.浏览器的原生 form 表单,如果不设置 enctype 属性,那么最终就会以 applica ...
