也許你需要一個特殊的Edit來限制浮點數的輸入,但是現有的Edit卻並不能完成這項工作——因為它只能夠單純的限制大小寫或者純數字。當你在論壇上求救的時候,某個網友告訴你:“用子類化。”你也許會在看到一線曙光的同時多出了一連串的問題:何為子類化?子類化的原理是什麼?如何實現子類化?下面就讓我從一個簡單的C++程序開始,一步步解開你的疑團吧。
首先,我為你列出以下這個C++程序:
#include <iostream>
using namespace std;
class Parent
{
public:
void func(void) { cout << "func of Parent" << endl; }
};
class Child : public Parent
{
public:
void func(void) { cout << "func of Child" << endl; }
};
void main()
{
Parent p;
Child c;
p.func();
c.func();
}
現在我來解說一下。這段代碼中我定義了兩個C++類:父類和子類,並且子類是繼承自父類的;它們有一個具有相同名稱的成員函數func。在main函數中,我分別構造了父類和子類的對象,並調用了它們各自的成員函數func。結果如下:
func of Parent
func of 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再次回到原來的回調過程中完成剩余的工作。
以上就是窗口子類化的原理分析,下面我通過一個實例來實際解說如何對窗口進行子類化。這個例子的界面如下:
界面上方的編輯框是用來限制浮點輸入的,下面則是一個普通的超級鏈接。
好了,下面我開始按步驟完成對這兩個窗口的子類化:
第一步,在主窗口對話框初始化的時候,保存原有的窗口過程,並設置新的窗口過程。代碼如下:
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以及退格鍵(注意不要少了退格鍵,否則你將會發現你的編輯框無法刪除輸入錯誤的數字)的時候,就發出一聲聲音以提示輸入錯誤。至於其它的消息,則調用原有的回調函數進行處理。
第三步,實現超級鏈接的窗口過程:
LRESULT CALLBACK ProcLink(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch (Msg)
{
case WM_SETCURSOR:
SetCursor(LoadCursor(NULL, IDC_HAND));
break;
case WM_LBUTTONDOWN:
ShellExecute(NULL, "open", "http://home.ncust.edu.cn/~titilima", NULL, NULL, SW_SHOWNORMAL);
break;
default:
return CallWindowProc(StaticProc, hWnd, Msg, wParam, lParam);
}
return 0;
}
這段代碼很容易明白:它完成了兩件事,其一是設置光標指針為手形(注意:對於較早的Windows系統,是沒有預定義的IDC_HAND指針的,這個時候你需要在EXE的資源中自己畫一個手形指針,比如Delphi之中的手形指針就是自己畫的),其二是當單擊了鼠標左鍵的時候打開你想打開的網頁鏈接。
其實對於超級鏈接,它更主要的東西是在子類化之外實現的——就是它的字體顏色(注意這段代碼是在主窗口對話框的回調過程中實現的):
case WM_CTLCOLORSTATIC:
if (GetDlgItem(hDlg, IDC_ST_HOMEPAGE) == (HWND)lParam)
{
SetTextColor((HDC)wParam, 0xff0000);
SetBkMode((HDC)wParam, TRANSPARENT);
return (LRESULT)CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
}
break;
還有幾點要說明的是:
1、你的這個Static超鏈接必須擁有一個唯一的資源ID,比如我的這個就是IDC_ST_HOMEPAGE,這樣才能用GetDlgItem獲得它的句柄來完成子類化;
2、你必須為它設置SS_NOTIFY樣式,以保證在單擊它的時候它能夠通知父窗口對話框;
3、單擊它打開網頁的處理也可以放在子類化之外,處理主窗口對話框的WM_COMMAND消息也可以實現這一功能。
本文配套源碼