子類化 = **攔截目標視窗的特定訊息,自行處理後,選擇性轉交原視窗程序**。類比 C++ 繼承覆寫虛函式:
class Parent { void func() { cout << "Parent"; } };
class Child : Parent { void func() { cout << "Child"; } }; // 覆寫
Win32 無繼承機制,改用**視窗程序指標替換**達成同樣目的。
WNDPROC g_OldEditProc; // 全域/靜態保存原程序
// 初始化時替換
g_OldEditProc = (WNDPROC)SetWindowLong(hEdit, GWL_WNDPROC, (LONG)NewEditProc);
// 新視窗程序
LRESULT CALLBACK NewEditProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
if (msg == WM_CHAR) { // 攔截字元輸入
if (wp != '.' && (wp < '0' || wp > '9') && wp != VK_BACK) {
MessageBeep(MB_OK); return 0; // 擋下非數字/小數點/退格
}
}
return CallWindowProc(g_OldEditProc, hWnd, msg, wp, lp); // 轉交原程序
}
關鍵點:
GWL_WNDPROC 索引對應視窗結構內的程序指標CallWindowProc 正確處理 Unicode/ANSI 轉換與堆疊清理WM_INITDIALOG
case WM_INITDIALOG:
g_OldEditProc = (WNDPROC)SetWindowLong(GetDlgItem(hDlg, IDC_EDIT),
GWL_WNDPROC, (LONG)FloatEditProc);
g_OldStaticProc = (WNDPROC)SetWindowLong(GetDlgItem(hDlg, IDC_ST_LINK),
GWL_WNDPROC, (LONG)LinkStaticProc);
break;
FloatEditProc
LRESULT CALLBACK FloatEditProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
if (msg == WM_CHAR) {
// 允許:0-9、小數點(一次)、退格鍵
if (wp != '.' && (wp < '0' || wp > '9') && wp != VK_BACK) {
MessageBeep(MB_OK); return 0;
}
if (wp == '.') { // 簡易防重複小數點(完整版需檢查現有文字)
// 可加強:GetWindowText 檢查是否已含 '.'
}
}
return CallWindowProc(g_OldEditProc, hWnd, msg, wp, lp);
}
LinkStaticProc
LRESULT CALLBACK LinkStaticProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
switch (msg) {
case WM_SETCURSOR:
SetCursor(LoadCursor(NULL, IDC_HAND)); // 手型游標
break;
case WM_LBUTTONDOWN:
ShellExecute(NULL, "open", "https://example.com", NULL, NULL, SW_SHOWNORMAL);
break;
default:
return CallWindowProc(g_OldStaticProc, hWnd, msg, wp, lp);
}
return 0;
}
WM_CTLCOLORSTATIC 設定外觀
case WM_CTLCOLORSTATIC:
if ((HWND)lp == GetDlgItem(hDlg, IDC_ST_LINK)) {
SetTextColor((HDC)wp, RGB(255,0,0)); // 紅字
SetBkMode((HDC)wp, TRANSPARENT);
return (LRESULT)GetSysColorBrush(COLOR_BTNFACE);
}
break;
必要條件:靜態文字控制項須設 SS_NOTIFY 樣式,才會接收 WM_LBUTTONDOWN 等滑鼠訊息。
| 議題 | 建議 |
|---|---|
| 64 位元相容 | 用 SetWindowLongPtr/GetWindowLongPtr + GWLP_WNDPROC,指標寬度正確 |
| 多執行緒 | 子類化僅影響呼叫執行緒的視窗;跨執行緒 UI 需 PostMessage 封送 |
| 清理 | 視窗銷毀前 SetWindowLong(..., g_OldProc) 還原,防止記憶體洩漏/崩潰 |
| MFC 專案 | 優先用 CWnd::SubclassWindow/CSubclassWnd 封裝,自動管理生命週期 |
子類化是 Win32 客製化系統控制項的核心技術。掌握 SetWindowLongPtr → 攔截訊息 → CallWindowProc 三部曲,即可在不重寫控制項的前提下,注入任意行為:輸入限制、視覺特效、滑鼠互動、無障礙增強等。配合 MFC 封裝類別,工程化維護更輕鬆。