C# 窗口过程消息处理 WndProc

C# 窗口过程消息处理 WndProc

WinForm WndProc

在 WinForm 中一般采用重写 WndProc 的方法对窗口或控件接受到的指定消息进行处理

示例:禁止通过关闭按钮或其他发送 WM_CLOSE 消息的途径关闭窗口

csharp 复制代码
protected override void WndProc(ref Message m)
{
    const int WM_CLOSE = 0x0010;
    if(m.Msg == WM_CLOSE)
    {
        // MessageBox.Show("禁止关闭此窗口");
        return;
    }
    base.WndProc(ref m);
}

Control 类中还有个 DefWndProc 为默认的窗口过程

WPF HwndSource

WPF 仅本机窗口或 HwndHost 嵌入控件拥有句柄,可通过 HwndSource 添加消息处理

示例:禁止通过关闭按钮或其他发送 WM_CLOSE 消息的途径关闭窗口

csharp 复制代码
HwndSource source = null;

protected override void OnSourceInitialized(EventArgs e)
{
    base.OnSourceInitialized(e);
    IntPtr handle = new WindowInteropHelper(this).Handle;
    source = HwndSource.FromHandle(handle);
    source.AddHook(WndProc);
}

protected override void OnClosed(EventArgs e)
{
    source?.RemoveHook(WndProc);
    base.OnClosed(e);
}

private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    const int WM_CLOSE = 0x0010;
    if(msg == WM_CLOSE)
    {
        // MessageBox.Show("禁止关闭此窗口");
        handled = true; // 标记为已处理
    }
    return IntPtr.Zero;
}

WinForm IMessageFilter

⚠ 注意:1.消息过滤器对于特定线程是唯一的;2.使用消息过滤器可能会降低程序性能

IMessageFilter 接口允许程序在将消息调度到控件或窗口之前捕获消息进行预处理

IMessageFilter 的 PreFilterMessage 与 Control 的 WndProc 接收到的消息是一个交集关系,应用程序接收到的消息来自系统消息队列,相对来说更全,但会有部分消息会直接发送到窗口或控件而不进入系统消息队列

实现 IMessageFilter 接口实例可对整个线程消息循环进行预处理,并根据 m.HWnd 获取消息传入的窗口或控件句柄

示例:截获程序鼠标悬浮消息,窗口标题显示当前悬浮控件名

csharp 复制代码
static class Program
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        var filter = new SampleMsgFilter();
        Application.AddMessageFilter(filter); // 添加到消息泵
        Application.Run(new MainForm());
        Application.RemoveMessageFilter(filter); // 从消息泵移除
    }
}

sealed class SampleMsgFilter : IMessageFilter
{
    public bool PreFilterMessage(ref Message m)
    {
        const int WM_MOUSEHOVER = 0x02A1;
        if(m.Msg == WM_MOUSEHOVER && Control.FromHandle(m.HWnd) is Control ctr)
        {
            ctr.FindForm().Text = ctr.Name;
            return true; // 过滤消息不继续派发
        }
        return false; // 允许消息派发到下一个过滤器或控件
    }
}

WinForm NativeWindow

NativeWindow 是 IWin32Window 的低级封装,并且和 WinForm Control 一样拥有 WndProc 和 DefWndProc 方法,故同样可通过重写 WndProc 方法处理消息

可以通过 CreateHandle(new CreateParams()) 创建没有 UI 的仅消息循环的窗口。比如托盘图标类 NotifyIcon 内部会创建一个 NativeWindow 用来接收任务栏创建消息 WM_TASKBARCREATED ("TaskbarCreated"),在资源管理器崩溃重启后重新创建图标。

附加到其他窗口

由于 WinForm Control WndProc 是密封的,处理消息时必须继承类型并重写,需要单独进行消息处理的窗口或控件较多时,对原代码具有很大的侵入性;而 IMessageFilter 是针对整个应用程序的消息循环,官方文档说使用消息过滤器很可能会降低程序性能;相对来说,由于 HwndSource AddHook 和 RemoveHook 不是密封的,WPF 程序可以在不侵入原代码的条件下处理窗口消息,在可复用性上面反而还具有优势。但如果仔细看看 NativeWindow 源代码,会发现它内部调用了 SetWindowLong GWL_WNDPROC (窗口子类化),可以通过 AssignHandle 附加到任意窗口或控件进行消息处理,这个窗口不限制类型,甚至可以附加到其他程序窗口。

这里提供一个静态辅助类,借助 NativeWindow 简化附加窗口消息过程处理操作:

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace Wondershare.WinTool.Helpers
{
  public delegate bool HookProc(ref Message m);

    public static class MessageHooker
    {
        sealed class HookWindow : NativeWindow
        {
            List<KeyValuePair<HookProc, Action>> hooks;

            public HookWindow(IntPtr hWnd)
            {
                AssignHandle(hWnd);
            }

            public void AddHookProc(HookProc hook, Action removedHandler)
            {
                if (hooks == null)
                {
                    hooks = new List<KeyValuePair<HookProc, Action>>();
                }
                hooks.Insert(0, new KeyValuePair<HookProc, Action>(hook, removedHandler));
            }

            public void RemoveHookProc(HookProc hook)
            {
                if (hooks != null)
                {
                    for (int i = hooks.Count - 1; i >= 0; i--)
                    {
                        if (hooks[i].Key == hook)
                        {
                            hooks[i].Value?.Invoke();
                            hooks.RemoveAt(i);
                        }
                    }
                }
            }

            protected override void WndProc(ref Message m)
            {
                if (hooks != null)
                {
                    foreach (var hook in hooks)
                    {
                        if (hook.Key(ref m)) return;
                    }
                    const int WM_NCDESTORY = 0x0082;
                    if (m.Msg == WM_NCDESTROY) // 窗口销毁时移除所有 hook
                    {
                        for (int i = hooks.Count - 1; i >= 0; i--)
                        {
                            hooks[i].Value?.Invoke();
                        }
                        hooks = null;
                    }
                    base.WndProc(ref m);
                }
            }
        }

        /// <summary>附加消息处理过程到窗口</summary>
        /// <param name="handle">需要附加消息处理过程的窗口句柄</param>
        /// <param name="hook">消息处理过程</param>
        /// <param name="removedHandler">消息处理过程移除回调</param>
        public static void AddHook(IntPtr handle, HookProc hook, Action removedHandler = null)
        {
            if (!(NativeWindow.FromHandle(handle) is HookWindow window))
            {
                window = new HookWindow(handle);
            }
            window.AddHookProc(hook, removedHandler);
        }

        /// <summary>从窗口移除附加的消息处理过程</summary>
        /// <param name="handle">需要移除消息处理过程的窗口句柄</param>
        /// <param name="hook">消息处理过程</param>
        public static void RemoveHook(IntPtr handle, HookProc hook)
        {
            if (NativeWindow.FromHandle(handle) is HookWindow window)
            {
                window.RemoveHookProc(hook);
            }
        }
    }
}
相关推荐
c#上位机1 小时前
wpf之RelativeSource用法总结
c#·wpf
玖笙&3 天前
✨WPF编程基础【2.1】布局原则
c++·wpf·visual studio
玖笙&3 天前
✨WPF编程基础【2.2】:布局面板实战
c++·wpf·visual studio
SEO-狼术3 天前
.NET WPF 数据编辑器集合提供列表框控件
.net·wpf
FuckPatience7 天前
WPF 具有跨线程功能的UI元素
wpf
诗仙&李白7 天前
HEFrame.WpfUI :一个现代化的 开源 WPF UI库
ui·开源·wpf
He BianGu7 天前
【笔记】在WPF中Binding里的详细功能介绍
笔记·wpf
He BianGu7 天前
【笔记】在WPF中 BulletDecorator 的功能、使用方式并对比 HeaderedContentControl 与常见 Panel 布局的区别
笔记·wpf
123梦野8 天前
WPF——效果和可视化对象
wpf
He BianGu8 天前
【笔记】在WPF中Decorator是什么以及何时优先考虑 Decorator 派生类
笔记·wpf