Windows是一個基於消息的系統,消息在Windows的對象之間進行著傳遞。子類化和Windows的鉤子機制存在於消息系統之中,我們可以利用這些機制來操縱、修改甚至丟棄那些在操作系統或是進程中傳遞的消息,以求改變系統的一些行為。
子類化技術用來截取窗口或控件之間的消息,當然是消息在到達目的窗口之前完成的操作。這些被截獲的消息既可以保留也可以修改它們的狀態,之後就繼續發送到目的地。子類化技術實現了一些正常情況下無法實現的功能,試想鼠標右鍵單擊TextBox,系統默認彈出Undo、Cut、Copy、Paste等菜單,我們就可以利用子類化技術來改變這個系統菜單。
簡單的說,子類化就是創建一個新的窗口消息處理過程,並將其插入到原先的默認窗口消息處理過程之前。子類化分為三類:實例子類化(instance subclassing)—從窗口或控件的單一實例截獲消息,這種子類化技術最普遍;全局子類化(global subclassing)—能夠截獲從相同的窗口類創建出來的多個窗口或控件的消息;超類化(superclassing)—和全局子類化很類似,區別在於可以應用在新的窗口類上面。
首先,我們看看這個C++程序:
#include <iOStream>
using namespace std;
class Parent
{
public:
void func { cout << "Parent" << endl; }
};
class Child : public Parent
{
public:
void func { cout << "Child" << endl; }
};
void main()
{
Parent p;
Child c;
p.func();
c.func();
}
現在我來解說一下。這段代碼中我定義了兩個C++類:父類和子類,並且子類是繼承自父類的;它們有一個具有相同名稱的成員函數func。在main函數中,我分別構造了父類和子類的對象,並調用了它們各自的成員函數func。結果如下:
Parent
Child
簡單說來,這段代碼就是子類根據自己的需要改寫了func成員函數。而Win32的子類化的原理也與此類似,只不過子類化實際上並沒有像C++一樣重載哪個函數,而是靠攔截Windows系統中的某些消息來自己進行處理罷了。舉例來說,請大家看以下這段簡單的窗口回調過程:
LRESULT CALLBACK ProcMain(HWND hDlg, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch (Msg)
{
case WM_CLOSE:
EndDialog(hDlg, 0);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return 0;
}
在這個回調之中,我手動處理了兩個消息:在單擊了“關閉”按鈕(WM_CLOSE)的時候,我將對話框關閉(EndDialog);在對話框銷毀(WM_DESTROY)的時候,我向系統消息隊列中發送了退出的消息來完成結束工作(PostQuitMessage)。也就是說,如果把WM_CLOSE的響應代碼改成:
case WM_CLOSE:
ShowWindow(hDlg, SW_MINIMIZE);
break;
這樣一來,這個對話框就會和MSN一樣,在單擊了“關閉”之後,就會完成最小化的工作了。那麼,對於窗口過程已定義好的系統控件,將如何手動響應它的消息呢?
我們可以用函數指針的辦法,將我們感興趣的消息攔截下來,處理完之後再讓預定義的窗口過程處理。這個過程大致如下:
WNDPROC OldProc;
OldProc = (WNDPROC)SetWindowsLong(hWnd, GWL_WNDPROC, (LONG)NewProc);
當然,這裡的新窗口過程NewProc是預先由你實現好的。上述代碼執行以後,系統在處理hWnd的窗口消息時,就會先進入你實現的NewProc回調過程,然後在處理過你感興趣的消息之後,通過CallWindowProc函數和你預先保存的OldProc再次回到原來的回調過程中完成剩余的工作。
以上就是窗口子類化的原理分析,下面我通過一個實例來實際解說如何對窗口進行子類化。當我們需要一個特殊的Edit來限制浮點數的輸入,但是現有的Edit卻並不能完成這項工作——因為它只能夠單純的限制大小寫或者純數字。就可以采用“子類化”。
下面我開始按步驟完成對這兩個窗口的子類化:
第一步,在主窗口對話框初始化的時候,保存原有的窗口過程,並設置新的窗口過程。代碼如下:
case WM_INITDIALOG:
EditProc = (WNDPROC)SetWindowLong(GetDlgItem(hDlg, IDC_EDIT), GWL_WNDPROC, (LONG)ProcFloat);
StaticProc = (WNDPROC)SetWindowLong(GetDlgItem(hDlg, IDC_ST_HOMEPAGE), GWL_WNDPROC, (LONG)ProcLink);
break;
第二步,實現浮點編輯框的窗口過程:
LRESULT CALLBACK ProcFloat(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
if (Msg == WM_CHAR && wParam != '.' && (wParam <= '0' || wParam >= '9') && wParam != VK_BACK)
{
MessageBeep(MB_OK);
return 0;
}
else
return CallWindowProc(EditProc, hWnd, Msg, wParam, lParam);
}
這裡需要解釋的是,由於控件本身的需求,所以只需要攔截一個消息,就是接收字符的WM_CHAR。當用戶輸入的字符不是小數點、0~9以及退格鍵(注意不要少了退格鍵,否則你將會發現你的編輯框無法刪除輸入錯誤的數字)的時候,就發出一聲聲音以提示輸入錯誤。至於其它的消息,則調用原有的回調函數進行處理。
子類化的限制:因子類化是對已存在的某一窗口產生作用,所以其作用范圍只有這一窗,又由於可能不清楚該類怎樣使用額外的類和窗口字節,所以不能保證正確使用這些空間存儲信息,最後,因窗口已存在,所以新的窗口過程永遠不會接收到第一個WM_CREATE消息或其他以前的消息。子類化只適用於改變極少數窗口行為和屬性時使用。