C# 窗口过程消息处理 WndProc的方法详解

 更新时间:2025年04月02日 08:53:22   作者:蓝点lilac  
在WinForm中一般采用重写WndProc的方法对窗口或控件接受到的指定消息进行处理,本文给大家介绍C#窗口过程消息处理WndProc的方法详解,感兴趣的朋友一起看看吧

WinForm WndProc

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

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

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 消息的途径关闭窗口

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 获取消息传入的窗口或控件句柄

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

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 简化附加窗口消息过程处理操作:

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# 窗口过程消息处理 WndProc的方法详解的文章就介绍到这了,更多相关C# 消息处理 WndProc内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C#接口(Interface)用法分析

    C#接口(Interface)用法分析

    这篇文章主要介绍了C#接口(Interface)用法,较为详细的分析了C#中接口的功能、实现及使用技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-03-03
  • js验证电话号码手机号码的正则表达式

    js验证电话号码手机号码的正则表达式

    本篇文章主要是对js验证电话号码手机号码的正则表达式进行了介绍。需要的朋友可以过来参考下,希望对大家有所帮助
    2014-01-01
  • c#中实现图片灰度化技术详解

    c#中实现图片灰度化技术详解

    这篇文章主要介绍了c#中实现图片灰度化技术详解,本文给出计算公式和实现代码以及图片例子,需要的朋友可以参考下
    2014-08-08
  • C# 多线程更新界面的错误的解决方法

    C# 多线程更新界面的错误的解决方法

    这篇文章主要介绍了C# 多线程更新界面的错误方法,由于一个线程的程序,如果调用一个功能是阻塞的,那么就会影响到界面的更新,导致使用人员操作不便。所以往往会引入双线程的工作的方式,主线程负责更新界面和调度,而次线程负责做一些阻塞的工作,便有了下面春雨里方法
    2021-10-10
  • C#实现winform中RichTextBox在指定光标位置插入图片的方法

    C#实现winform中RichTextBox在指定光标位置插入图片的方法

    这篇文章主要介绍了C#实现winform中RichTextBox在指定光标位置插入图片的方法,涉及RichTextBox控件及剪切板的相关操作技巧,非常简单实用,需要的朋友可以参考下
    2016-06-06
  • C#泛型的逆变协变之个人理解

    C#泛型的逆变协变之个人理解

    这篇文章主要介绍了C#泛型的逆变协变之个人理解,需要的朋友可以参考下
    2023-04-04
  • 解决C#中WebBrowser的DocumentCompleted事件不执行的实现方法

    解决C#中WebBrowser的DocumentCompleted事件不执行的实现方法

    本篇文章是对C#中WebBrowser的DocumentCompleted事件不执行解决方法进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • C#判断字符串中内容是否为纯数字的详细教程

    C#判断字符串中内容是否为纯数字的详细教程

    在进行C#编程时候,有的时候我们需要判断一个字符串是否是数字字符串,下面这篇文章主要给大家介绍了关于C#判断字符串中内容是否为纯数字的详细教程,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-04-04
  • C#关键字in、out、ref的作用与区别

    C#关键字in、out、ref的作用与区别

    这篇文章介绍了C#关键字in、out、ref的作用与区别,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-04-04
  • WPF自定义实现雷达图控件的示例详解

    WPF自定义实现雷达图控件的示例详解

    雷达图用于表示不同内容的占比关系,在项目中有广泛的应用,但是目前未曾有封装良好的雷达图控件,所以本文分享了如何封装一个通用的雷达图控件,希望对大家有所帮助
    2023-08-08

最新评论