MsgFilterProc 是 Windows 訊息過濾鉤子 (WH_MSGFILTER) 的回調函數。與全域的 WH_SYSMSGFILTER 不同,WH_MSGFILTER 是執行緒局部的鉤子,僅監控安裝它的執行緒中的模態迴圈訊息 (對話方塊、訊息方塊、功能表、捲軸)。它無需 DLL,可直接在應用程式中安裝,適合為應用程式自身的模態 UI 添加熱鍵、自定義導航、攔截特定按鍵等功能。
LRESULT CALLBACK MsgFilterProc(
_In_ int nCode,
_In_ WPARAM wParam,
_In_ LPARAM lParam
);
| 參數 | 類型 | 說明 |
|---|---|---|
| nCode | int | 模態迴圈類型代碼:MSGF_DIALOGBOX (0):對話方塊 (DialogBox/DialogBoxParam/DialogBoxIndirect)MSGF_MESSAGEBOX (1):訊息方塊 (MessageBox/MessageBoxEx)MSGF_MENU (2):功能表 (TrackPopupMenu/TrackPopupMenuEx)MSGF_SCROLLBAR (5):捲軸操作MSGF_NEXTWINDOW (6):視窗切換 (Alt+Tab/Alt+Esc)MSGF_MAX (8):最大值標記 |
| wParam | WPARAM | 訊息 ID (如 WM_KEYDOWN、WM_COMMAND、WM_MOUSEWHEEL) |
| lParam | LPARAM | 指向 MSG 結構的指標,包含完整訊息資訊 (hwnd, message, wParam, lParam, time, pt) |
nCode < 0:必須返回 CallNextHookEx(hHook, nCode, wParam, lParam)| 特性 | WH_MSGFILTER | WH_SYSMSGFILTER |
|---|---|---|
| 作用範圍 | 僅當前執行緒的模態迴圈 | 全系統所有執行緒的模態迴圈 |
| 安裝需求 | 同進程,無需 DLL | 必須在 DLL 中,注入所有進程 |
| 攔截目標 | 自身應用的 DialogBox/MessageBox/Menu | 所有進程的系統模態 UI |
| 典型用途 | 應用內部熱鍵、對話框增強、無障礙 | 系統級熱鍵、全域無障礙、UI 自動化 |
| 效能影響 | 極低 | 較高 (跨進程) |
unit MsgFilterHookUnit;
interface
uses Windows, Messages, Classes, SysUtils;
type
THotKeyAction = procedure(HotKeyID: Integer) of object;
TMsgFilterHook = class
private
FHook: HHOOK;
FHotKeys: array[1..10] of record
Key: Word; // 虛擬鍵碼
Modifiers: Integer; // MOD_CONTROL, MOD_ALT, MOD_SHIFT, MOD_WIN
Action: THotKeyAction;
Active: Boolean;
end;
FHotKeyCount: Integer;
class var FInstance: TMsgFilterHook;
class function HookProc(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
function CheckHotKey(const Msg: TMsg): Boolean;
public
constructor Create;
destructor Destroy; override;
procedure Install;
procedure Uninstall;
function RegisterHotKey(Key: Word; Modifiers: Integer; Action: THotKeyAction): Integer;
procedure UnregisterHotKey(Index: Integer);
end;
implementation
{ TMsgFilterHook }
constructor TMsgFilterHook.Create;
begin
inherited;
FInstance := Self;
FHotKeyCount := 0;
end;
destructor TMsgFilterHook.Destroy;
begin
Uninstall;
FInstance := nil;
inherited;
end;
class function TMsgFilterHook.HookProc(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
var
pMsg: PMsg;
begin
Result := 0;
if (nCode >= 0) and Assigned(FInstance) then
begin
// 只處理按鍵訊息
if (wParam = WM_KEYDOWN) or (wParam = WM_SYSKEYDOWN) then
begin
pMsg := PMsg(lParam);
if FInstance.CheckHotKey(pMsg^) then
begin
Result := 1; // 攔截該按鍵,不讓對話框處理
Exit;
end;
end;
end;
Result := CallNextHookEx(FInstance.FHook, nCode, wParam, lParam);
end;
function TMsgFilterHook.CheckHotKey(const Msg: TMsg): Boolean;
var
i: Integer;
ModifiersMatch: Boolean;
begin
Result := False;
for i := 1 to FHotKeyCount do
begin
if not FHotKeys[i].Active then Continue;
if Msg.wParam <> FHotKeys[i].Key then Continue;
// 檢查修飾鍵
ModifiersMatch := True;
if (FHotKeys[i].Modifiers and MOD_CONTROL) <> 0 then
ModifiersMatch := ModifiersMatch and (GetAsyncKeyState(VK_CONTROL) < 0);
if (FHotKeys[i].Modifiers and MOD_ALT) <> 0 then
ModifiersMatch := ModifiersMatch and (GetAsyncKeyState(VK_MENU) < 0);
if (FHotKeys[i].Modifiers and MOD_SHIFT) <> 0 then
ModifiersMatch := ModifiersMatch and (GetAsyncKeyState(VK_SHIFT) < 0);
if (FHotKeys[i].Modifiers and MOD_WIN) <> 0 then
ModifiersMatch := ModifiersMatch and (GetAsyncKeyState(VK_LWIN) < 0 or GetAsyncKeyState(VK_RWIN) < 0);
if ModifiersMatch then
begin
// 執行動作 (在主執行緒安全執行)
if Assigned(FHotKeys[i].Action) then
begin
// 使用 PostMessage 避免在鉤子回調中直接執行複雜邏輯
PostMessage(Application.MainFormHandle, WM_USER + 100, i, 0);
end;
Result := True;
Break;
end;
end;
end;
procedure TMsgFilterHook.Install;
begin
if FHook = 0 then
FHook := SetWindowsHookEx(WH_MSGFILTER, @HookProc, 0, GetCurrentThreadId);
// 第3參數 0 = 當前進程模組句柄 (EXE 即可)
// 第4參數 = 當前執行緒 ID
end;
procedure TMsgFilterHook.Uninstall;
begin
if FHook <> 0 then
begin
UnhookWindowsHookEx(FHook);
FHook := 0;
end;
end;
function TMsgFilterHook.RegisterHotKey(Key: Word; Modifiers: Integer; Action: THotKeyAction): Integer;
begin
Result := -1;
if FHotKeyCount >= 10 then Exit;
Inc(FHotKeyCount);
FHotKeys[FHotKeyCount].Key := Key;
FHotKeys[FHotKeyCount].Modifiers := Modifiers;
FHotKeys[FHotKeyCount].Action := Action;
FHotKeys[FHotKeyCount].Active := True;
Result := FHotKeyCount;
end;
procedure TMsgFilterHook.UnregisterHotKey(Index: Integer);
begin
if (Index >= 1) and (Index <= FHotKeyCount) then
FHotKeys[Index].Active := False;
end;
end.
unit MainUnit;
interface
uses Windows, Forms, StdCtrls, Controls, Classes, MsgFilterHookUnit;
type
TForm1 = class(TForm)
btnShowDialog: TButton;
btnShowMsgBox: TButton;
btnInstallHook: TButton;
btnUninstallHook: TButton;
memoLog: TMemo;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure btnShowDialogClick(Sender: TObject);
procedure btnShowMsgBoxClick(Sender: TObject);
procedure btnInstallHookClick(Sender: TObject);
procedure btnUninstallHookClick(Sender: TObject);
private
MsgFilterHook: TMsgFilterHook;
procedure WndProc(var Msg: TMessage); override;
procedure OnHotKeyAction(HotKeyID: Integer);
procedure ShowCustomDialog;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
MsgFilterHook := TMsgFilterHook.Create;
// 註冊熱鍵:Ctrl+F1 = 顯示幫助, Alt+F4 = 阻止關閉, Ctrl+S = 快速儲存
MsgFilterHook.RegisterHotKey(VK_F1, MOD_CONTROL, OnHotKeyAction); // ID=1
MsgFilterHook.RegisterHotKey(VK_F4, MOD_ALT, OnHotKeyAction); // ID=2
MsgFilterHook.RegisterHotKey(Ord('S'), MOD_CONTROL, OnHotKeyAction); // ID=3
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
MsgFilterHook.Free;
end;
procedure TForm1.btnInstallHookClick(Sender: TObject);
begin
MsgFilterHook.Install;
memoLog.Lines.Add('[MsgFilter Hook Installed]');
end;
procedure TForm1.btnUninstallHookClick(Sender: TObject);
begin
MsgFilterHook.Uninstall;
memoLog.Lines.Add('[MsgFilter Hook Uninstalled]');
end;
procedure TForm1.WndProc(var Msg: TMessage);
begin
if Msg.Msg = WM_USER + 100 then
begin
OnHotKeyAction(Msg.WParam);
Exit;
end;
inherited;
end;
procedure TForm1.OnHotKeyAction(HotKeyID: Integer);
begin
case HotKeyID of
1: // Ctrl+F1
begin
memoLog.Lines.Add('[HotKey] Ctrl+F1: 顯示線上說明');
// Application.HelpCommand(HELP_FINDER, 0);
end;
2: // Alt+F4
begin
memoLog.Lines.Add('[HotKey] Alt+F4: 攔截關閉對話框');
// 返回非零值已在鉤子中處理,對話框不會收到 WM_SYSCOMMAND/SC_CLOSE
end;
3: // Ctrl+S
begin
memoLog.Lines.Add('[HotKey] Ctrl+S: 快速儲存');
// SaveCurrentDocument();
end;
end;
end;
procedure TForm1.btnShowDialogClick(Sender: TObject);
begin
// 測試:顯示一個模態對話框,嘗試按 Ctrl+F1, Alt+F4, Ctrl+S
ShowCustomDialog;
end;
procedure TForm1.btnShowMsgBoxClick(Sender: TObject);
begin
// 測試:訊息方塊也會觸發鉤子 (MSGF_MESSAGEBOX)
MessageBox(Handle, '測試 MsgFilter Hook\n按 Ctrl+F1 或 Alt+F4', '測試', MB_OK or MB_ICONINFORMATION);
end;
procedure TForm1.ShowCustomDialog;
var
Dlg: TForm;
begin
Dlg := TForm.Create(nil);
try
Dlg.Caption := '測試對話框 - 按熱鍵試試';
Dlg.Position := poScreenCenter;
Dlg.BorderStyle := bsDialog;
Dlg.ClientWidth := 300;
Dlg.ClientHeight := 150;
with TLabel.Create(Dlg) do
begin
Parent := Dlg;
Caption := '熱鍵測試:\nCtrl+F1 = 幫助\nAlt+F4 = 攔截關閉\nCtrl+S = 儲存';
Left := 20; Top := 20; Width := 260; Height := 80;
end;
Dlg.ShowModal;
finally
Dlg.Free;
end;
end;
end.
// 在 HookProc 中處理 MSGF_MENU
if nCode = MSGF_MENU then
begin
pMsg := PMsg(lParam);
if (pMsg.message = WM_KEYDOWN) then
begin
case pMsg.wParam of
VK_F1: // F1 顯示功能表項說明
begin
// 獲取當前功能表控制代碼
// 發送自定義訊息顯示工具提示
Result := 1;
Exit;
end;
VK_ESCAPE: // ESC 關閉功能表 (系統預設行為,可攔截修改)
begin
// 自定義邏輯
end;
'A'..'Z': // 字母鍵快速選擇功能表項
begin
// 模擬功能表助憶鍵行為
end;
end;
end;
end;
// 攔截捲軸鍵盤操作,實現自定義捲動行為
if nCode = MSGF_SCROLLBAR then
begin
pMsg := PMsg(lParam);
if (pMsg.message = WM_KEYDOWN) then
begin
case pMsg.wParam of
VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT,
VK_PRIOR, VK_NEXT, VK_HOME, VK_END:
begin
// 可修改捲動步長、添加平滑捲動動畫
// 返回 1 攔截原始捲動,自行發送 WM_VSCROLL/WM_HSCROLL
end;
end;
end;
end;
WH_MSGFILTER。PostMessage。Show 顯示的視窗不走模態迴圈,不會觸發此鉤子。MessageBox、DialogBox 等系統 API 建立的模態迴圈會觸發;但 TForm.ShowModal 內部也是呼叫 DialogBox,同樣有效。WH_MSGFILTER,按安裝順序逆序調用 (最後安裝最先調用)。SetWindowsHookEx(WH_MSGFILTER, proc, 0, GetCurrentThreadId) - 安裝執行緒局部訊息過濾鉤子CallNextHookEx - 傳遞鉤子鏈UnhookWindowsHookEx - 移除鉤子GetAsyncKeyState - 即時查詢修飾鍵狀態IsDialogMessage - 系統內部處理對話框鍵盤導航 (可參考實現自定義導航)TrackPopupMenuEx - 觸發 MSGF_MENU 迴圈MsgFilterProc 配合 WH_MSGFILTER 是為應用程式自身模態 UI (對話框、訊息框、功能表、捲軸) 添加全域熱鍵、自定義鍵盤導航、攔截特定按鍵的輕量級方案。優勢在於:無需 DLL、僅影響自身執行緒、安裝簡單、效能開銷極低。關鍵掌握:nCode 區分迴圈類型、wParam 識別訊息、lParam 獲取完整 MSG 結構、返回非零攔截訊息。適用於增強現有應用的模態介面交互體驗。