雖然 WinForms 採用事件驅動模型,但底層仍是 Windows 訊息迴路。透過 P/Invoke 呼叫 `SendMessage`/`PostMessage`,可實現跨進程通訊、控制項行為修改、模擬輸入等進階功能。本文完整說明訊息定義、發送、接收攔截、與 `IMessageFilter` 全域過濾技術。
public static class Win32Messages
{
// 滑鼠
public const int WM_LBUTTONDOWN = 0x0201;
public const int WM_LBUTTONUP = 0x0202;
public const int WM_MOUSEMOVE = 0x0200;
public const int WM_MOUSEWHEEL = 0x020A;
// 鍵盤
public const int WM_KEYDOWN = 0x0100;
public const int WM_KEYUP = 0x0101;
public const int WM_CHAR = 0x0102;
// 視窗
public const int WM_CLOSE = 0x0010;
public const int WM_DESTROY = 0x0002;
public const int WM_PAINT = 0x000F;
// 自訂訊息範圍
public const int WM_USER = 0x0400; // 0x0400 ~ 0x7FFF 供視窗類別私用
public const int WM_APP = 0x8000; // 0x8000 ~ 0xBFFF 應用程式全域
}
using System.Runtime.InteropServices;
public static class NativeMethods
{
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SendMessage(
IntPtr hWnd, // 目標視窗句柄
int Msg, // 訊息代碼
IntPtr wParam, // 參數 1
IntPtr lParam // 參數 2
);
// 針對特定訊息的強型別多載(如 WM_SETTEXT)
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(
IntPtr hWnd, int Msg, IntPtr wParam, string lParam);
// 非同步發送(不等待處理完成)
[DllImport("user32.dll")]
public static extern bool PostMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
}
// 取得目標視窗句柄(可用 FindWindow/FindWindowEx/EnumWindows)
IntPtr hBtn = FindWindowEx(parentHandle, IntPtr.Zero, "Button", "確定");
// 發送 BM_CLICK (0x00F5) 訊息模擬點擊
NativeMethods.SendMessage(hBtn, 0x00F5, IntPtr.Zero, IntPtr.Zero);
public class MyForm : Form
{
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case Win32Messages.WM_LBUTTONDOWN:
// 攔截左鍵按下
Console.WriteLine($"滑鼠左鍵按下: X={m.LParam.ToInt32() & 0xFFFF}, Y={m.LParam.ToInt32() >> 16}");
// 不呼叫 base.WndProc 即可「吃掉」訊息
return;
case Win32Messages.WM_CLOSE:
// 關閉前確認
if (MessageBox.Show("確定關閉?", "確認", MessageBoxButtons.YesNo) == DialogResult.No)
return; // 取消關閉
break;
}
base.WndProc(ref m); // 傳遞給基類處理
}
}
應用程式層級攔截**所有表單**的訊息(如全域熱鍵、禁用滑鼠移動):
public class GlobalMouseFilter : IMessageFilter
{
public bool PreFilterMessage(ref Message m)
{
if (m.Msg == Win32Messages.WM_MOUSEMOVE)
{
// 返回 true 表示已處理,訊息不再分發
return true;
}
return false; // 繼續正常分發
}
}
// 在 Program.cs 主入口註冊
static void Main()
{
Application.AddMessageFilter(new GlobalMouseFilter());
Application.Run(new MainForm());
}
針對單一控制項攔截訊息(類似 MFC SubclassWindow):
public class NativeWindowListener : NativeWindow
{
private Control _target;
public NativeWindowListener(Control target)
{
_target = target;
AssignHandle(target.Handle); // 關聯 Handle
}
protected override void WndProc(ref Message m)
{
if (m.Msg == Win32Messages.WM_KEYDOWN && (Keys)m.WParam == Keys.Enter)
{
Console.WriteLine("該控制項偵測到 Enter");
// 可選:阻止進一步處理
// return;
}
base.WndProc(ref m);
}
public void Release() => ReleaseHandle();
}
| 需求 | 訊息/方法 | 說明 |
|---|---|---|
| 設定控制項文字 | WM_SETTEXT (0x000C) | SendMessage(hWnd, WM_SETTEXT, 0, "新文字") |
| 取得控制項文字 | WM_GETTEXT (0x000D) | 需配合 StringBuilder 接收 |
| 啟用/停用控制項 | WM_ENABLE (0x000A) | wParam=1 啟用, 0 停用 |
| 模擬鍵盤輸入 | WM_KEYDOWN/UP/CHAR | 需正確填入虛擬鍵碼 |
| 讓視窗置頂 | SetWindowPos HWND_TOPMOST | 非 SendMessage,需 P/Invoke SetWindowPos |
| 跨進程傳資料 | WM_COPYDATA (0x004A) | 配合 COPYDATASTRUCT 結構 |
Vanara.PInvoke.User32 (NuGet) 提供完整強型別簽名,避免手寫 DllImport 錯誤。`SendMessage` + `WndProc`/`IMessageFilter` 是 WinForms 觸達 Windows 核心訊息機制的橋樑。掌握訊息代碼、`wParam`/`lParam` 解碼、64 位 P/Invoke 規範,即可實現控制項行為擴展、跨進程通訊、全域攔截等高階功能。**原則:能用 Managed API 解決時,優先用 Managed API;必須落入訊息層時,嚴謹處理型別與生命週期。**