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

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

由于 WPF 路由事件(主要是隧道和冒泡)的存在,我们很容易能够通过只监听窗口中的某些事件使得整个窗口中所有控件发生的事件都被监听到。然而,如果我们希望监听的是整个应用程序中所有的事件呢?路由事件的路由可并不会跨越窗口边界呀?

本文将介绍我编写的应用程序窗口监视器,来监听整个应用程序中所有窗口中的路由事件。这样的方法可以用来无时无刻监视 WPF 程序的各种状态。


其实问题依旧摆在那里,因为我们依然无法让路由事件跨越窗口边界。更麻烦的是,我们甚至不知道应用程序有哪些窗口,这些窗口都是什么时机显示出来的。

Application 类中有一个属性 Windows,这是一个 WindowCollection 类型的属性,可以用来获取当前已经被 Application 类管理的所有的窗口的集合。当然 Application 类内部还有一个属性 NonAppWindowsInternal 用来管理与此 Application 没有逻辑关系的窗口集合。

于是,我们只需要遍历 Windows 集合便可以获得应用程序中的所有窗口,然后对每一个窗口监听需要的路由事件。

var app = Application.Current;
foreach (Window window in app.Windows)
{
    // 在这里监听窗口中的事件。
}

等等!这种操作意味着将来新打开的窗口是不会被监听到事件的。

我们有没有方法拿到新窗口的显示事件呢?遗憾的是——并不行。

但是,我们有一些变相的处理思路。比如,由于 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)
{
    // 当前激活的窗口已经发生了改变,可以在这里为新的窗口做一些事情了。
}

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));
        }
    }
}

使用方法是:

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

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

另外,我在 ApplicationWindowMonitor 的构造函数中加入了一个过滤窗口的委托。比如你可以让窗口的监听只对主要的几个窗口生效,而对一些信息提示窗口忽略等等。


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

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

知识共享许可协议

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

相关文章:

  • 如何追踪 WPF 程序中当前获得键盘焦点的元素并显示出来
  • 使用 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 特性来按条件编译代码的不同原理和适用场景
  • [js高手之路]搞清楚面向对象,必须要理解对象在创建过程中的内存表示
  • 4个实用的微服务测试策略
  • ES6系统学习----从Apollo Client看解构赋值
  • express + mock 让前后台并行开发
  • Java的Interrupt与线程中断
  • JAVA多线程机制解析-volatilesynchronized
  • jquery ajax学习笔记
  • 成为一名优秀的Developer的书单
  • 嵌入式文件系统
  • 使用iElevator.js模拟segmentfault的文章标题导航
  • 网页视频流m3u8/ts视频下载
  • 用element的upload组件实现多图片上传和压缩
  • 在Docker Swarm上部署Apache Storm:第1部分
  • 格斗健身潮牌24KiCK获近千万Pre-A轮融资,用户留存高达9个月 ...
  • 树莓派用上kodexplorer也能玩成私有网盘
  • 完善智慧办公建设,小熊U租获京东数千万元A+轮融资 ...
  • #我与Java虚拟机的故事#连载07:我放弃了对JVM的进一步学习
  • (6)【Python/机器学习/深度学习】Machine-Learning模型与算法应用—使用Adaboost建模及工作环境下的数据分析整理
  • (C#)if (this == null)?你在逗我,this 怎么可能为 null!用 IL 编译和反编译看穿一切
  • (delphi11最新学习资料) Object Pascal 学习笔记---第5章第5节(delphi中的指针)
  • (编程语言界的丐帮 C#).NET MD5 HASH 哈希 加密 与JAVA 互通
  • (深入.Net平台的软件系统分层开发).第一章.上机练习.20170424
  • (五)MySQL的备份及恢复
  • (转)mysql使用Navicat 导出和导入数据库
  • (转)Scala的“=”符号简介
  • (转)项目管理杂谈-我所期望的新人
  • (轉貼) 寄發紅帖基本原則(教育部禮儀司頒布) (雜項)
  • *2 echo、printf、mkdir命令的应用
  • .CSS-hover 的解释
  • .net core MVC 通过 Filters 过滤器拦截请求及响应内容
  • .NET 编写一个可以异步等待循环中任何一个部分的 Awaiter
  • .NET 设计模式—简单工厂(Simple Factory Pattern)
  • .NET 使用 JustAssembly 比较两个不同版本程序集的 API 变化
  • .xml 下拉列表_RecyclerView嵌套recyclerview实现二级下拉列表,包含自定义IOS对话框...
  • @property @synthesize @dynamic 及相关属性作用探究
  • @Service注解让spring找到你的Service bean
  • [.NET]桃源网络硬盘 v7.4
  • [30期] 我的学习方法
  • [④ADRV902x]: Digital Filter Configuration(发射端)
  • [Asp.net MVC]Bundle合并,压缩js、css文件