首先講講什麼是子類化,其實子類化很好理解,和以前一樣,仍然從win32 sdk方法開始,在這裡也可以補充一下,我在一些群裡的見到有些人關於MFC的說法,說直接就學MFC就可以了,沒必要學win32,有的人把MFC說的很簡單似的,其實不然,由於MFC對底層的隱藏和其復雜的框架,其實很多時候,我們學起來是很吃力的,而且,很多人照著一些編程書說的照著做,改來改去,最後做出來了,但是不知道其中的原理,當需要自己設計程序的時候,不知無從下手。從我自己學習windows桌面程序開發以來,發現不管是NET Framework,MFC,ATL,MFC ACTIVEX,MFC automation等等,其實要真正理解和學習,都要從win32開始,因為它們的底層就是它們,只有知道了他們,才能對MFC進行靈活的應用,在遇到錯誤的時候,可以分析程序,找到問題所在。也許你不會用到win32自己去編寫,但是對於你理解是很很有幫助的。好了,言歸正傳。
先來看看在win32中,程序在窗口過程中處理各種行為,如用戶的操作了消息,或是系統產生的消息,都在窗口過程中進行消息處理。那麼子類化其實就是在消息到達窗口過程之前,再插入一個過程處理。原來我們只有一個窗口過程,有關這個窗口的消息,都在這個窗口過程中進行處理。但是現在我們在消息達到這個過程之前,進行一些處理,再將消息送到原來的窗口過程。在win32中,設置這個插入的窗口過程,是使用函數SetWindowLong。函數原型為:
LONG WINAPI SetWindowLong(
_In_ HWND hWnd,
_In_ int nIndex,
_In_ LONG dwNewLong
);
我們知道在win32創建窗口(參見本博客“MFC-序幕”)過程中,第一步是聲明一個WNDCLASS窗口類,然後填充成員,這樣就設置了我們要創建的窗口的一些屬性,如菜單,光標,程序圖標等等,其中有一個成員就是設置窗口過程,完成之後,我們用RegisterClass函數注冊這個窗口類(也可以用別的函數,還有一些MFC函數,但原理一樣),之後我們就可以用這個窗口類創建窗口,那麼用這個窗口類創建的窗口的消息,都會到達窗口類中設置的窗口過程函數中。而一個窗口是用句柄類標識的,因此在這裡,SetWindowLong的第一個參數是窗口句柄,說明要在它的原來注冊的窗口過程之前,要插入一個窗口過程函數,第二個參數是一個索引值,這個函數本來是用來改變窗口的注冊窗口類中的屬性的,如樣式,實例句柄等等,索引值就是用來說明要修改的什麼屬性,其中,修改這個窗口指向的窗口過程,而這個新指向的窗口過程其中一個最重要的就是對不處理的消息傳遞給原來的窗口過程函數,第三個參數就是新值。成功之後,當消息來的時候,就會先到我們插入的這個新的窗口過程,然後再到原來的窗口過程。如下:
{
if (uMsg == WM_GETDLGCODE)
return DLGC_WANTALLKEYS;
return CallWindowProc(OrigProc, hwnd, uMsg, wParam, lParam);
}
這裡就將不處理的消息傳遞給了原來的窗口過程。
那為什麼要進行子類化呢,其實是為了在原來窗口過程處理之前,我們可以做一些預處理,或是攔截一些消息,不讓傳給原始窗口過程,或是有些消息,必須要子類化的,才可以處理的,如對話框上的控件,要處理WM_LBUTTONDOWN,WM_SETCURSOR等等標准的窗口消息,這些消息,如果我們不用子類化處理,直接傳遞給對話框,那麼對話框就會進行處理之後,我們就只能處理控件的通知消息,如click等消息。
在MFC中在使用的窗口過程函數加switch/case語句來處理消息,而是使用窗口類加消息映射函數來處理,其實道理是一樣。也就是它們對消息處理方式上的不同而已。在文章“MFC--消息”中說明了MFC的消息,也是結合win32來說明的,有興趣的可以看看。下面以一個例子來說明MFC中的子類化。
首先我建立一個基於對話框的程序,然後在對話框上放了6個編輯框,ID分別是IDC_EDIT1到IDC_EDIT6,然後放了兩個按鈕,ID分別是IDC_BUTTON1和IDC_BUTTON2。如下圖:
對於編輯框,有時候,我們希望當輸入完成之後,我們按enter鍵之後,就完成一個編輯框的輸入,而自動轉到下一個編輯框,就不用使用鼠標或是tab鍵來移動編輯框的輸入焦點,但是編輯框中對回車鍵的響應要麼是換行(多行樣式)或是不響應(單行樣式),現在我設置的這個是單行輸入,要實現enter鍵的時候,自動把焦點移動到下一個編輯框。下面兩個按鈕,對於我們畫在對話框上的按鈕,默認的鼠標指針形狀是箭頭指針,現在我要實現當把鼠標移動到“手形指針”按鈕上的時候,變成手形指針。要實現這些,就要通過子類化來處理響應的消息來實現。
上面已經描述了,MFC中使用的窗口類和消息響應來處理消息的,現在我要將編輯框和按鈕的消息到達對話框消息處理之前,對編輯框和按鈕的消息進行處理,因為控件仍然窗口,也有標准的窗口消息,如WM_MOUSEMOVE,WM_LBUTTONDOWN等等。因此,先添加兩個MFC類,CMyEdit和CMyButton,它們分別繼承於CEdit和CButton,添加之後如下:
在主對話框頭文件中包含兩個頭文件:
#include MyEdit.h
#include MyButton.h
在主對話框中聲明對應的變量:
在主對話框的OnInitDialog中,設置子類化:
這樣,當在控件上所有消息,會首先經過剛才我們添加的窗口中,然後才會到對話框中。現在我們先實現編輯框的輸入焦點通過enter鍵來移動。
當一個窗口獲取焦點的時候,會收到消息WM_SETFOCUS,當一個窗口失去焦點的時候,會收到WM_KILLFOCUS。因此我處理這兩個消息,在CMyEdit類中設置一個布爾值,判斷該窗口是否有焦點,當獲取焦點的時候,就設置為TRUE,當失去焦點的時候為FALSE。如下:
接著再響應WM_KEYUP消息,來過濾enter鍵按下的情況。並且我設置了一個自定義消息,向主對話框發送消息,讓它來通過每個編輯框對象中的布爾值來判斷當前那個編輯框有焦點,然後使用SetFocus函數將焦點到下一個編輯框。如下:
當收到enter彈起的消息之後,向主對話框發送一個自定義消息WM_SETCONTROLFOCUS,接著對這個自定義消息進行處理,如下:
CheckFocus是我聲明和定義的一個函數,在當中處理輸入焦點的轉換。這樣就完成了這樣一個功能。
下面實現將一個按鈕上講鼠標指針變換成手形,那麼首先要說明指針。鼠標指針默認情況是使用在注冊窗口類的時候,所設置的指針類型,當然,在對話框中,不是這樣,對話框是預定義的一種窗口類。如果要在不改變窗口類屬性的情況下修改鼠標指針的形狀,包括普通窗口和對話框,控件等類型的窗口,可以處理WM_SETCURSOR消息,如果我們不處理這個消息,那麼系統會使用默認的鼠標指針,如果要使用我們自己的指針,那麼就可以在這個消息中,設置我們自己的指針,完成之後,還要設置返回值為非0值,告知系統我們提供了我們自己的指針,不用系統為我們提供指針了。好了,知道這個原理之後,我們在CMyButton中添加消息處理WM_SETCURSOR消息,作如下處理:
這樣就實現了將這個子類化的button上當鼠標移到的時候,變成手形的鼠標指針。上面通過這個例子,說明子類化的應用,但是最重要的是理解子類化的原理,那麼在以後自己程序設計的過程中,知道應該如何利用子類化,而子類化的應用這裡的例子只是為了說明它的原理,而其應用完全不止這些。
而CWnd的SubclassWindow這個函數呢,要注意的一點,那個窗口在使用子類化之前沒有綁定到某個MFC對象之上,也就是說一個HWND還沒有被MFC對象使用Attach將其於一個MFC對象綁定。另外,通過 UnsubclassWindow函數可以解除對某個窗口的子類化。對於MFC 對話框控件,還有一個函數,也可以方便的使用子類化,SubclassDlgItem,這個函數的第一個參數是控件的ID,第二個參數是其父窗口對象指針。這個效果和SubclassWindow一樣,只是參數不一樣,使用SubclassWindow要自己通過win32的GetDlgItem獲取控件的窗口句柄。而SubclassDlgItem不用。
更多信息,參考msdn。