原文:如何监视 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_ActivatedWindow_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/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系

发布了382 篇原创文章 · 获赞 232 · 访问量 47万+

如何监视 WPF 中的所有窗口,在所有窗口中订阅事件或者附加 UI的更多相关文章

  1. WPF中任务栏只显示主窗口

    我们在用WPF开发的时候,常常会遇到在主窗口打开的情况下,去显示子窗口,而此时任务栏同时显示主窗口与子窗口.这样看起来很不美观.所以在弹出子窗口之前,设置它的几个相应属性,便不会出现这种问题了. // ...

  2. 【WPF】监听WPF的WebBrowser控件弹出新窗口的事件

    原文:[WPF]监听WPF的WebBrowser控件弹出新窗口的事件 WPF中自带一个WebBrowser控件,当我们使用它打开一个网页,例如百度,然后点击它其中的链接时,如果这个链接是会弹出一个新窗 ...

  3. 【WPF】在新线程上打开窗口

    当WPF应用程序运行时,默认会创建一个UI主线程(因为至少需要一个),并在该UI线程上启动消息循环.直到消息循环结束,应用程序就随即退出.那么,问题就来了,能不能创建新线程,然后在新线程上打开一个新窗 ...

  4. WPF中的Visual Tree和Logical Tree与路由事件

    1.Visual Tree和Logical TreeLogical Tree:逻辑树,WPF中用户界面有一个对象树构建而成,这棵树叫做逻辑树,元素的声明分层结构形成了所谓的逻辑树!!Visual Tr ...

  5. WPF编程,使用WindowChrome实现自定义窗口功能的一种方法。

    原文:WPF编程,使用WindowChrome实现自定义窗口功能的一种方法. 版权声明:我不生产代码,我只是代码的搬运工. https://blog.csdn.net/qq_43307934/arti ...

  6. WPF 学习笔记-设置属性使窗口不可改变大小

    原文:WPF 学习笔记-设置属性使窗口不可改变大小 调整Windows下的ResizeMode属性: ResizeMode = NoResize Resize属性是控制Windows是否可以改变大小, ...

  7. vim中project多标签和多窗口的使用

    1.打开多个窗口 打开多个窗口的命令以下几个: 横向切割窗口 :new+窗口名(保存后就是文件名) :split+窗口名,也可以简写为:sp+窗口名 纵向切割窗口名 :vsplit+窗口名,也可以简写 ...

  8. 第十三篇:在SOUI中使用有窗口句柄的子窗口

    前言: 无论一个DirectUI系统提供的DUI控件多么丰富,总会有些情况下用户需要在DUI窗口上放置有窗口句柄的子窗口. 为了和无窗口句柄的子窗口相区别,这里将有窗口句柄的子窗口称之为真窗口. 每一 ...

  9. HTML中IFrame父窗口与子窗口相互操作

    一.Iframe篇 //&&&&&&&&&&&&&&&&&&am ...

随机推荐

  1. linux高性能服务器编程 (七) --Linux服务器程序规范

    第七章 LInux 服务器程序规范 1)linux服务器程序一般以后台进程形式运行.后台进程又称为守护进程,是没有控制终端的,所以不会受到外界的干扰.守护进程的父进程通常是init进程(PID为1的进 ...

  2. Content-type解析

    一.是什么? 是Http的实体首部字段,用于说明请求或返回的消息主体是用何种方式编码,在request header和response header里都存在. Content-Type(内容类型),一 ...

  3. 【Gamma】设计与计划

    目录 需求分析 已实现 功能 用户使用动机分析 当前阶段推广困难 当前阶段任务优先级 主要功能解析 社团活动场地申请 - 实现成本较高,正在调研社长的需求 完善入社流程的信息提示 通知功能 通知管理 ...

  4. Mercurial 安装及使用

      版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/moonspiritacm/articl ...

  5. windows 10环境下安装Tensorflow-gpu

    网上有很多教程,特别是简写上的写的都还算比较详细.但我自己还是遇到了几个坑,希望对深度学习有兴趣的同学遇到跟我一样的坑,希望这份记录能帮助到你. 问题一:要不要使用Anaconda? 我看极客时间上的 ...

  6. mysql知识集锦

    1.mysql中InnoDB引擎中页的概念 2.mysql索引详解--如何从磁盘中读取索引文件

  7. C# Mysql 查询 Rownum

    SELECT @rownum:=@rownum+1 AS rownum,a.order_id ,case when a.Ordered =1 then '已分单' end as Ordered,a.p ...

  8. Hadoop的三种调度器FIFO、Capacity Scheduler、Fair Scheduler(转载)

    目前Hadoop有三种比较流行的资源调度器:FIFO .Capacity Scheduler.Fair Scheduler.目前Hadoop2.7默认使用的是Capacity Scheduler容量调 ...

  9. cisco 路由与ASA SSH 设置

    转载于https://www.cnblogs.com/sun292393989/p/8980700.html 一 试验拓扑 二 Server配置 ①配置hostname和domain name 因为r ...

  10. Hive、Inceptor数据倾斜详解及解决

    一.倾斜造成的原因 正常的数据分布理论上都是倾斜的,就是我们所说的20-80原理:80%的财富集中在20%的人手中, 80%的用户只使用20%的功能 , 20%的用户贡献了80%的访问量. 俗话是,一 ...