一、什么是窗口類 在Windows中運行的程序,大多數(shù)都有一個或幾個可以看得見的窗口,而在這些窗口被創(chuàng)建起來之前,操作系統(tǒng)怎么知道該怎樣創(chuàng)建該窗口,以及用戶操作該窗口的各種消息交給誰處理呢?所以VC在調(diào)用Windows的API(CreateWindow或者CreateWindowEx)創(chuàng)建窗口之前,要求程序員必須定義一個窗口類(不是傳統(tǒng)C++意義上的類)來規(guī)定所創(chuàng)建該窗口所需要的各種信息,主要包括:窗口的消息處理函數(shù)、窗口的風格、圖標、 鼠標、菜單等。其定義如下:
typedef struct tagWNDCLASSA(注:該結(jié)構(gòu)為ANSII版本) { UINT style ; WNDPROC lpfnWndProc ; int cbClsExtra ; int cbWndExtra ; HINSTANCE hInstance ; HICON hIcon ; HCURSOR hCursor ; HBRUSH hbrBackground ; LPCSTR lpszMenuName ; LPCSTR lpszClassName ; }WNDCLASSA, * PWNDCLASSA, NEAR * NPWNDCLASSA, FAR * LPWNDCLASSA ;
style 表示該類窗口的風格,如style = CS_VREDRAW|CS_HREDRAW表示窗口在運動或者調(diào)整大小時需要重畫,關(guān)于其它風格可在 MSDN中查到。 lpfnWndProc為一指針,指向用戶定義的該窗口的消息處理函數(shù)。 cbClsExtra 用于在窗口類結(jié)構(gòu)中保留一定空間,用于存在自己需要的某些信息。 cbWndExtra用于在Windows內(nèi)部保存的窗口結(jié)構(gòu)中保留一定空間。 hInstance 表示創(chuàng)建該窗口的程序的運行實體代號(WinMain的參數(shù)之一)。 hIcon、hCursor、hbrBackground、lpszMenuName分別表示該窗口的圖標、鼠標形狀、背景色以及菜單。 lpszClassName表示該窗口類別的名稱,即標識該窗口類的標志。 從上面可以看出一個窗口類就對應(yīng)一個WNDCLASSA結(jié)構(gòu)(這里以ANSII為例),當程序員將該結(jié)構(gòu)按自己要求填寫完成后,就可以調(diào)用RegisterClass(或RegisterClassEx)函數(shù)將該類注冊,這樣以后凡是要創(chuàng)建該窗口,只需要以該類名(lpszClassName中指定)為參數(shù)調(diào)用CreateWindow,你看多方便呀,真是一舉多得??! 總結(jié):但窗口結(jié)構(gòu)注冊(調(diào)用RegisterClass(或RegisterClassEx)函數(shù))后,以后凡是要創(chuàng)建該窗口,只需要以該類名(lpszClassName中指定)為參數(shù)調(diào)用CreateWindow。 二、傳統(tǒng)SDK中的窗口類 既然我們知道了什么是窗口類,那我們就將它放到一個傳統(tǒng)的SDK程序中,看看是怎樣運行的。 #include <windows.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("HelloWin") ; WNDCLAS wndclass ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuNam = NULL ; wndclass.lpszClassName = szAppName ;
RegisterClass (&wndclass);
hwnd = CreateWindow( szAppName, // window class name TEXT ("The Hello Program"), // window caption WS_OVERLAPPEDWINDOW, // window style CW_USEDEFAULT, // initial x position CW_USEDEFAULT, // initial y position CW_USEDEFAULT, // initial x size CW_USEDEFAULT, // initial y size NULL, // parent window handle NULL, // window menu handle hInstance, // program instance handle NULL) ; // creation parameters ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; }
這是一個標準的Windows程序代碼,程序被啟動后,填寫一個窗口類,然后調(diào)用RegisterClass將該類注冊,接著創(chuàng)建該窗口,最后顯示窗口和進入消息循環(huán)。
三、MFC中的窗口類 當你看到這里,也許你可能會感到奇怪:我在用MFC向?qū)ё龀绦驎r,并沒有進行什么窗口類的填寫和注冊嗎?是的,你沒有,但是向?qū)湍阕隽?。在展示向?qū)窃趺醋龅闹?,請讓我先介紹一下預先知識。 在MFC系統(tǒng)中定義了五個默認的窗口類(這里不包括AFX_WNDCOMMCTLS_REG),分別定義在AFXIMPL.h中: #define AFX_WND_REG (0x0001) #define AFX_WNDCONTROLBAR_REG (0x0002) #define AFX_WNDMDIFRAME_REG (0x0004) #define AFX_WNDFRAMEORVIEW_REG (0x0008) #define AFX_WNDDOLECONTROL_REG (0x0020)
在WINCORE.cpp定義了這些窗口類對應(yīng)的字符串名稱: const TCHAR _afxWnd[] = AFX_WND; const TCHAR _afxWndControlBar[] = AFX_WNDCONTROLBAR; const TCHAR _afxWndMDIFrame[] = AFX_WNDMDIFRAME; const TCHAR _afxWndFrameOrView[] = AFX_WNDFRAMEORVIEW; const TCHAR _afxWndOleControl[] = AFX_WNDOLERONTROL;
在AFXIMPL.h中定義了五個AFX_XXX對應(yīng)的字符串: #define AFX_WND AFX_WNDCLASS("WND") #define AFX_WNDCONTROLBAR AFX_WNDCLASS("ControlBar") #define AFX_WNDMDIFRAME AFX_WNDCLASS("MDIFrame") #define AFX_WNDFRAMEORVIEW AFX_WNDCLASS("FrameOrView") #define AFX_WNDOLECONTROL AFX_WNDCLASS("OleControl")
看到這里也許有些心急了,其實上面一堆代碼只是定義了五個默認窗口類的字符串名稱和二進制名稱,具體注冊行為在全局函數(shù)AfxDeferRegisterClass中: #define AfxDeferRegisterClass(fClass) / ((afxRegisteredClasses & fClass) ? TRUE:AfxEndDeferRegisterClass(fClass) #define afxRegisteredClasses AfxGetModuleState()->m_fRegisteredClasses
BOOL AFXAPI AfxEndDeferRegisterClass(short fClass) { WNDCLASS wndCls; wndCls.lpfnWndProc = DefWindowProc; if(fClass & AFX_WND_REG) { wndCls.lpszClassName=_afxWnd; AfxRegisterClass(&wndCls); }else if(fClass & AFX_WNDOLECONTROL_REG) { wndCls.lpszClassName=_afxWndOleControl; AfxRegisterClass(&wndCls); }else if(fClass & AFX_WNDCONTROLBAR_REG) { wndCls.lpszClassName=_afxWndControlBar; AfxRegisterClass(&wndCls); }else if(fClass & AFX_WNDMDIFRAME_REG) { RegisterWithIcon(&wndCls,_afxWndMDIFrame,AFX_IDI_MDIFRAME); }else if(fClass & AFX_WNDFRAMEORVIEW_REG) { RegisterWithIcon(&wndCls,_afxWndFrameOrView,AFX_IDI_STD_FRAME); }else if(fClass & AFX_WNDCOMMCTLS_REG) { InitCommonControls(); } } 從以上例子可以看出, AfxDeferRegisterClass函數(shù)用if/else結(jié)構(gòu)實現(xiàn)各種不同窗口的注冊,所所以MFC函數(shù)窗口注冊的時候調(diào)用AfxDeferRegisterClass函數(shù)就可以了。 從上面的代碼可以看出,AfxDeferRegisterClass函數(shù)首先判斷該窗口類是否注冊,如已注冊則直接返回,否則調(diào)用AfxEndDeferRegisterClass進行注冊,即注冊要求的默認窗口類。其中RegisterWithIcon和InitCommonControls最終也是轉(zhuǎn)化為調(diào)用AfxRegisterClass,而AfxRegisterClass函數(shù)調(diào)用RegisterClass進行注冊,啊,終于看到SDK中的RegisterClass了,看到它總有一種親切感! 有了上面的知識,我們就可以很容易摸清MFC是怎樣注冊窗口類的了!我們知道Windows上所有看得見的東西,在MFC中都是繼承于CWnd類的,而CWnd類創(chuàng)建窗口的成員函數(shù)是Create和CreateEx,由于Create最終是調(diào)用CreateEx,所以我們只需要看CreateEx函數(shù)就行了: create()-->CreateEx()??CREATESTRUCT PreCreateWindow(cs); |?? AfxDeferRegisterClass(AFX_WND_REG) CreateWindowEx()
BOOL CWnd::CreateEx(DWORD dwExStyle, LPCSTSTR lpszClassName, …… LPVOID lpParam) { CREATESTRUCT cs; cs.dwExStyle = dwExStyle; … … cs.lpCreateParams = lpParam;
PreCreateWindow(cs); AfxHookWindowCreate(this); HWND hWnd=::CreateWindowEx(cs.dwStyle,cs.lpszClass,…,cs.lpCreateParams); …… }
啊,一看到CreateWindowEx,親切感又來了,這不是和SDK中的CreateWindow一樣嘛,是創(chuàng)建窗口!既然這樣,那么注冊窗口肯定在該函數(shù)之前,會是誰呢?如果你做過一點MFC程序,你就會對將眼光停留PreCreateWindow上。對!就是它了。 PreCreateWindow函數(shù)是CWnd類的一個虛擬函數(shù),提供程序設(shè)置待創(chuàng)建窗口的屬性(包括窗口類),這樣繼承于CWnd的類都可以按照自己的要求在PreCreateWindow中設(shè)置自己的屬性了,而且我們看到MFC也是這樣做的: BOOL CWnd::PreCreateWindow(CREATESTRUCT &cs) { if(cs.lpszClass = = NULL) { AfxDeferRegisterClass(AFX_WND_REG); cs.lpszClass = _afxWnd; } return TRUE; }
BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT &cs) { if(cs.lpszClass = = NULL) { AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG); cs.lpszClass = _afxWndFrameOrView; } return TRUE; }
BOOL CMDIFrameWnd::PreCreateWindow(CREATESTRUCT &cs) { if(cs.lpszClass = = NULL) { AfxDeferRegisterClass(AFX_WNDMDIFRAME_REG); cs.lpszClass = _afxWndMDIFrame; } }
BOOL CMDIChildWnd::PreCreateWindow(CREATESTRUCT &cs) { return CFrameWnd::PreCreateWindow(cs); }
BOOL CView::PreCreateWindow(CREATESTRUCT &cs) { if(cs.lpszClass = = NULL) { AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG); cs.lpszClass = _afxWndFrameOrView; } }
就是通過繼承的方法,MFC實現(xiàn)常用類的窗口注冊(代碼并不完全,是從MFC中抽取對我們有意義的一部分代碼)。
四、在MFC中注冊自己的窗口類 在MFC中創(chuàng)建一個窗口,就必須是繼承于CWnd類的,這樣你的CMyWnd類自然就有了PreCreateWindow方法。你想注冊有自己個性的窗口類,那么就在該函數(shù)中進行吧。也就是在PreCreateWindow函數(shù)中注冊自己的窗口類,然后將窗口類的類名以及待創(chuàng)建窗口的其它屬性(見CREATESTRUCT結(jié)構(gòu))填寫cs,然后返回系統(tǒng),供系統(tǒng)創(chuàng)建你的窗口。
用SDK建立類的過程: 填寫一個窗口類,然后調(diào)用RegisterClass將該類注冊,接著創(chuàng)建該窗口,最后顯示窗口和進入消息循環(huán)。 用MFC建立窗口的過程: 我們知道Windows上所有看得見的東西,在MFC中都是繼承于CWnd類的,而CWnd類創(chuàng)建窗口的成員函數(shù)是Create和CreateEx,由于Create最終是調(diào)用CreateEx,所以我們只需要看CreateEx函數(shù)就行了: create()-->CreateEx()??CREATESTRUCT PreCreateWindow(cs); |?? AfxDeferRegisterClass(AFX_WND_REG) AfxHookWindowCreate(this); //為窗口關(guān)聯(lián)一個消息處理函數(shù)WndProc() CreateWindowEx() ********************************************************************************************** CWnd::CreateEX中HOOK函數(shù)作用
VC 2009-08-26 20:25 閱讀9 評論0 字號: 大大 中中 小小 用最基本的一句話概述,鉤子函數(shù)起了很大作用。故事是這樣的,有些漫長,也需要些耐心。
MFC中消息分為3類:
1. WM_COMMAND:所有的UI組件和加速鍵都會產(chǎn)生這種消息,所有派生于CCmdTarget的類都有能力處理該消息
2. 標準消息:除WM_COMMAND之外的WM_xx消息都是標準消息,派生于CWnd的類都有能力處理該消息
3. 控件通知消息:用于子窗口控件向父窗口發(fā)送的消息
在MFC的消息映射表的建立中,通過一組宏,你就可以讓自己的類先于父類處理某些Windows消息,這種行為很像虛函數(shù),只是我們重載的內(nèi)容不是虛函數(shù),而是消息。
推動消息的泵
第一階段 窗口過程 在產(chǎn)生一個窗口的時候,會調(diào)用CFrameWnd::Create,所有的故事也都從這里展開。下面的代碼為了簡潔,去掉了不相關(guān)的代碼 BOOL CFrameWnd::Create(…) { // … if ( ! CreateEx(…)) { // … } // … } BOOL CWnd::CreateEx(…) { // … AfxHookWindowCreate( this ); HWND hWnd = ::CreateWindowEx(…); // … }
void AFXAPI AfxHookWindowCreate(CWnd * pWnd) { // … if (pThreadState -> m_hHookOldCbtFilter == NULL) { pThreadState -> m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT, _AfxCbtFilterHook, NULL, ::GetCurrentThreadId()); // … } // … pThreadState -> m_pWndInit = pWnd; }
這樣,通過AfxHookWindowCreate,在當前線程中安裝了一個鉤子,用來攔截和窗口相關(guān)的事件,每當: 1. 另一個窗口成為active; 2. 產(chǎn)生或摧毀一個窗口 3. Minimize或maximize一個窗口; 4. 移動或縮放一個窗口; 5. 完成一個來自系統(tǒng)菜單的命令; 6. 從系統(tǒng)隊列中取出一個消息; 時,都會先調(diào)用_AfxCbtFilterHook(即每當有一個可能引發(fā)消息發(fā)生的事件的時候都會調(diào)用_AfxCbtFilterHook,然后這個函數(shù)對這些消息進行過濾,能夠處理的就交給AfxGetAfxWndProc,不能處理的就交給全局的DefWndProc()函數(shù)),接下來看看鉤子函數(shù)作了什么: LRESULT CALLBACK _AfxCbtFilterHook( int code, WPARAM wParam, LPARAM lParam) { // … WNDPROC afxWndProc = AfxGetAfxWndProc(); oldWndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC,(DWORD_PTR)afxWndProc); // … } WNDPROC AFXAPI AfxGetAfxWndProc() { // … return & AfxWndProc; }
這樣,_AfxCbtFilterHook的工作總結(jié)起來就是通過窗口子類化,把新建的窗口的窗口過程設(shè)置成AfxWndProc。 到這里,我們終于找到了窗口過程。 結(jié)論 CFrameWnd::Create創(chuàng)建窗口調(diào)用CWnd::CreateEx CWnd::CreateEx調(diào)用AfxHookWindowCreate準備為窗口設(shè)置鉤子 AfxHookWindowCreate調(diào)用::SetWindowHookEx為窗口設(shè)置了一個WH_CBT類型的鉤子來過濾消息,并把過濾函數(shù)設(shè)置成_AfxCbtFilterHook _AfxCbtFilterHook通過窗口子類化設(shè)置窗口的窗口過程為AfxWndProc 這樣,通過::DispatchMessage發(fā)送給窗口的消息就會源源不斷地送到AfxWndProc中來,可以想到,AfxWndProc利用MFC的消息映射表,分門別類的對消息進行分流。
即每當有一個可能引發(fā)消息發(fā)生的事件的時候都會調(diào)用_AfxCbtFilterHook,然后這個函數(shù)對這些消息進行過濾,能夠處理的就交給AfxGetAfxWndProc,不能處理的就交給全局的DefWndProc()函數(shù) OnNcCreate,當CWnd對象第一次被創(chuàng)建時,框架在WM_CREATE消息之前調(diào)用這個成員函數(shù)??梢孕薷腃REATESTRUCT結(jié)構(gòu),PreCreateWindow也是可以修改CREATESTRUCT 結(jié)構(gòu),他們有什么區(qū)別? PreCreateWindow用的比較多,OnNcCreate都用在什么地方??
OnNcCreate是響應(yīng)WM_NCCREATE, 當窗口開始時先創(chuàng)建客戶區(qū),所以先發(fā)送WM_NCCREATE消息, 當非客戶區(qū)都創(chuàng)建好了,再發(fā)送WM_CREATE,去創(chuàng)建窗口客戶區(qū), The WM_NCCREATE message is sent prior to the WM_CREATE message when a window is first created. 意思是說,WM_NCCREATE比WM_CREATE先發(fā)給窗口程序,在窗口一創(chuàng)建的時候 就是說: 0. call CreateWindow/CreateWindowEx開始 0.5 PreCreateWindow <--- HOOK(窗口句柄無效) 1. 窗口創(chuàng)建 2. WM_NCCREATE (窗口句柄有效) 3. WM_CREATE (窗口句柄有效) 4. call CreateWindow/CreateWindowEx結(jié)束 這些很容易驗證 [轉(zhuǎn)]CWnd中PreCreateWindow、PreSubclassWindow、SubclassWindow的區(qū)別 Posted on 2009-01-15 16:35 天之驕子 閱讀(503) 評論(0) 編輯 收藏 引用 MFC(VC6.0)的CWnd及其子類中,有如下三個函數(shù): class CWnd : public CCmdTarget { public: virtual BOOL PreCreateWindow(CREATESTRUCT& cs); virtual void PreSubclassWindow(); BOOL SubclassWindow(HWND hWnd); }; 讓人很不容易區(qū)分,不知道它們究竟干了些什么,在什么情況下要改寫哪個函數(shù)? 想知道改寫函數(shù)?讓我先告訴你哪個不能改寫,那就是SubclassWindow。Scott Meyers的杰作<<Effective C++>>的第36條是這樣的Differentiate between inheritance of interface and inheritance of implementation. 看了后你馬上就知道,父類中的非虛擬函數(shù)是設(shè)計成不被子類改寫的。根據(jù)有無virtual關(guān)鍵字,我們在排除了SubclassWindow后,也就知道PreCreateWindow和PreSubClassWindow是被設(shè)計成可改寫的。接著的問題便是該在什么時候該寫了。要知道什么時候該寫,必須知道函數(shù)是在什么時候被調(diào)用,還有執(zhí)行函數(shù)的想要達到的目的。我們先看看對這三個函數(shù),MSDN給的解釋: PreCreateWindow: Called by the framework before the creation of the Windows window attached to this CWnd object. (譯:在窗口被創(chuàng)建并attach到this指針所指的CWnd對象之前,被framework調(diào)用) PreSubclassWindow: This member function is called by the framework to allow other necessary subclassing to occur before the window is subclassed. (譯:在window被subclassed之前被framework調(diào)用,用來允許其它必要的subclassing發(fā)生) 雖然我已有譯文,但還是讓我對CWnd的attach和窗口的subclass作簡單的解釋吧!要理解attach,我們必須要知道一個C++的CWnd對象和窗口(window)的區(qū)別:window就是實在的窗口,而CWnd就是MFC用類對window所進行C++封裝。attach,就是把窗口附加到CWnd對象上操作。附加(attach)完成后,CWnd對象才和窗口發(fā)生了聯(lián)系。窗口的subclass是指修改窗口過程的操作,而不是面向?qū)ο笾械呐缮宇悺?br> 好了,PreCreateWindow由framework在窗口創(chuàng)建前被調(diào)用,函數(shù)名也說明了這一點,Pre應(yīng)該是previous的縮寫,PreSubclassWindow由framework在subclass窗口前調(diào)用。 這段話說了等于沒說,你可能還是不知道,什么時候該改寫哪個函數(shù)。羅羅嗦嗦的作者,還是用代碼說話吧!源碼之前,了無秘密(候捷語)。我們就看看MFC中的這三個函數(shù)都是這樣實現(xiàn)的吧! BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam) { // allow modification of several common create parameters CREATESTRUCT cs; cs.dwExStyle = dwExStyle; cs.lpszClass = lpszClassName; cs.lpszName = lpszWindowName; cs.style = dwStyle; cs.x = x; cs.y = y; cs.cx = nWidth; cs.cy = nHeight; cs.hwndParent = hWndParent; cs.hMenu = nIDorHMenu; cs.hInstance = AfxGetInstanceHandle(); cs.lpCreateParams = lpParam; if (!PreCreateWindow(cs)) { PostNcDestroy(); return FALSE; } AfxHookWindowCreate(this); HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass, cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy, cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams); return TRUE; } // for child windows BOOL CWnd::PreCreateWindow(CREATESTRUCT& cs) { if (cs.lpszClass == NULL) { // make sure the default window class is registered VERIFY(AfxDeferRegisterClass(AFX_WND_REG)); // no WNDCLASS provided - use child window default ASSERT(cs.style & WS_CHILD); cs.lpszClass = _afxWnd; } return TRUE; } CWnd::CreateEx先設(shè)定cs(CREATESTRUCT),在調(diào)用真正的窗口創(chuàng)建函數(shù)::CreateWindowEx之前,調(diào)用了CWnd::PreCreateWindow函數(shù),并把參數(shù)cs以引用的方式傳遞了進去。而CWnd的PreCreateWindow函數(shù)也只是給cs.lpszClass賦值而已。畢竟,窗口創(chuàng)建函數(shù)CWnd::CreateEx的諸多參數(shù)中,并沒有哪個指定了所要創(chuàng)建窗口的窗口類,而這又是不可缺少的(請參考<<windows程序設(shè)計>>第三章)。所以當你需要修改窗口的大小、風格、窗口所屬的窗口類等cs成員變量時,要改寫PreCreateWindow函數(shù)。 // From VS Install PathVC98MFCSRCWINCORE.CPP BOOL CWnd::SubclassWindow(HWND hWnd) { if (!Attach(hWnd)) return FALSE; // allow any other subclassing to occur PreSubclassWindow(); // now hook into the AFX WndProc WNDPROC* lplpfn = GetSuperWndProcAddr(); WNDPROC oldWndProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)AfxGetAfxWndProc()); ASSERT(oldWndProc != (WNDPROC)AfxGetAfxWndProc()); if (*lplpfn == NULL) *lplpfn = oldWndProc; // the first control of that type created #ifdef _DEBUG else if (*lplpfn != oldWndProc) { ::SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)oldWndProc); } #endif return TRUE; } void CWnd::PreSubclassWindow() { // no default processing } CWnd::SubclassWindow先調(diào)用函數(shù)Attach(hWnd)讓CWnd對象和hWnd所指的窗口發(fā)生關(guān)聯(lián)。接著在用::SetWindowLong修改窗口過程(subclass)前,調(diào)用了PreSubclassWindow。CWnd::PreSubclassWindow則是什么都沒有做。 在CWnd的實現(xiàn)中,除了CWnd::SubclassWindow會調(diào)用PreSubclassWindow外,還有一處。上面所列函數(shù)CreateEx的代碼,其中調(diào)用了一個AfxHookWindowCreate函數(shù),見下面代碼: // From VS Install PathVC98MFCSRCWINCORE.CPP BOOL CWnd::CreateEx( ) { // allow modification of several common create parameters if (!PreCreateWindow(cs)) { PostNcDestroy(); return FALSE; } AfxHookWindowCreate(this); HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass, cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy, cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams); return TRUE; } 接著察看AfxHookWindowCreate的代碼: // From VS Install PathVC98MFCSRCWINCORE.CPP void AFXAPI AfxHookWindowCreate(CWnd* pWnd) { if (pThreadState->m_hHookOldCbtFilter == NULL) { pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT, _AfxCbtFilterHook, NULL, ::GetCurrentThreadId()); if (pThreadState->m_hHookOldCbtFilter == NULL) AfxThrowMemoryException(); } } 其主要作用的::SetWindowsHookEx函數(shù)用于設(shè)置一個掛鉤函數(shù)(Hook函數(shù))_AfxCbtFilterHook,每當Windows產(chǎn)生一個窗口時(還有許多其它類似,請參考<<深入淺出MFC>>第9章,563頁),就會調(diào)用你設(shè)定的Hook函數(shù)。 這樣設(shè)定完成后,回到CWnd::CreateEx函數(shù)中,執(zhí)行::CreateWindowEx進行窗口創(chuàng)建,窗口一產(chǎn)生,就會調(diào)用上面設(shè)定的Hook函數(shù)_AfxCbtFilterHook。而正是在_AfxCbtFilterHook中對函數(shù)PreSubclassWindow進行了第二次調(diào)用。見如下代碼: // From VS Install PathVC98MFCSRCWINCORE.CPP /**////////////////////////////////////////////////////////////////////////////// // Window creation hooks LRESULT CALLBACK _AfxCbtFilterHook(int code, WPARAM wParam, LPARAM lParam) { // connect the HWND to pWndInit pWndInit->Attach(hWnd); // allow other subclassing to occur first pWndInit->PreSubclassWindow(); { // subclass the window with standard AfxWndProc oldWndProc = (WNDPROC)SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)afxWndProc); ASSERT(oldWndProc != NULL); *pOldWndProc = oldWndProc; } } 也在調(diào)用函數(shù)SetWindowLong進行窗口subclass前調(diào)用了PreSubclassWindow. 通常情況下窗口是由用戶創(chuàng)建的 CWnd::Create(..) ●在此流程中,MFC提供一個機會"PreCreateWindow()供用戶在創(chuàng)建前作點手腳
而對于對話框等,窗口是通過subclass方式交給用戶的 系統(tǒng)讀入對話框模板,建立其中各個子窗口
然后將各子窗口的 消息處理函數(shù)替換成 對應(yīng)的C++對象 的消息處理函數(shù) (Subclass:子類化,或"接管") ,然后,這個子窗口就會按類中定義的方式來動作了。
在此過程中,調(diào)用的是CWnd:SubclassWindow( HWND hWnd ); ●在此流程中,MFC提供一個機會"PreSubclassWindow" 供用戶在關(guān)聯(lián)前作點手腳
具體來說,如果你定義一個窗口(如CButton派生類CMyButton),然后使用對話框數(shù)據(jù)交換將一個按鈕與自己的派生類對象關(guān)聯(lián),這時候,一些"建立前"的處理就應(yīng)該寫在"PreSubclassWindow"中。
如果你用的不是"對話框數(shù)據(jù)關(guān)聯(lián)",而是在OnInitDialg中自己創(chuàng)建m_mybtn.Create(...) 這時候,一些"建立前"的處理就應(yīng)該寫在 "PreCreateWindow"中。 這里“建立前”的處理包括像那些處理,跟PreCreateWindows()做的一些窗口初始化的工作有什么不同? PreCreateWindows函數(shù)中沒有窗口可以用——還沒有創(chuàng)建 PreSubclassWindow函數(shù)中可以對窗口進行操作。 ****************************** 這些在窗口創(chuàng)建之初就加入了鉤子,能否截獲這些鉤子。
------------------以下內(nèi)容是對上面內(nèi)容的具體解釋,兩部分必須結(jié)合著看--------------------------------------
MFC的窗口類(如CWnd)與窗口過程。 Windows是基于事件機制的,任何窗口都能發(fā)送和處理消息,每一個窗口都對應(yīng)著自己的消息處理函數(shù),即通常所說的窗口過程(WindowProc)。窗口過程通常是在WNDCLASSEX的lpfnWndProc變量中指定的,然后調(diào)用RegisterClassEx注冊窗口類,lpfnWndProc要求是全局的或是類的靜態(tài)成員,而MFC的窗口和類對象是一一對應(yīng)的,在類中定義的窗口過程(CWnd::WindowProc)并非類的靜態(tài)成員,那么窗口消息是怎樣傳給窗口對象的WindowProc函數(shù)去處理的呢?MFC中定義了一個全局的AfxWndProc函數(shù),AfxWndProc是MFC中所有的窗口共用的窗口過程。這里要注意在AfxEndDeferRegisterClass中注冊窗口類時并沒有把AfxWndProc賦給lpfnWndProc,而是把DefWindowProc賦給了lpfnWndProc。真正把AfxWndProc指定為窗口過程的是在CWnd::CreateEx函數(shù)中,CWnd::CreateEx中先后調(diào)用了SetWindowsHookEx、CreateWindowEx和UnhookWindowsHookEx,SetWindowsHookEx安裝了一個WH_CBT類型的鉤子,在調(diào)用CreateWindowEx時(在CreateWindowEx返回之前)窗口會發(fā)送WM_CREATE、 WM_NCCREATE等消息,鉤子過程CBTProc會在窗口消息WM_CREATE、 WM_NCCREATE等發(fā)送前被調(diào)用,并提前得到窗口的句柄值。鉤子過程CBTProc的任務(wù)是把窗口句柄賦給窗口對象(CWnd::m_hWnd),并調(diào)用SetWindowLong把窗口過程替換成AfxWndProc(如是控件還要保留原窗口過程,用CallWindowProc進行默認處理)。在這有人可能會問,為什么不在AfxEndDeferRegisterClass中直接指定AfxWndProc呢?當然是有原因的:其一是控件的窗口過程必須用SetWindowLong來替換,其二是消息WM_CREATE、 WM_NCCREATE等是在CreateWindowEx返回前發(fā)送的,CWnd::WindowProc在處理這些消息時CWnd::m_hWnd必須是已經(jīng)被初始化的,這個就是由前面的CBTProc完成的。 好現(xiàn)在我們只要關(guān)注AfxWndProc了。AfxWndProc是如何把消息分配給各個窗口對象的窗口過程的呢?在MFC中有一個全局的映射表(還沒到消息映射,呵呵),這個表是窗口句柄到窗口對象的映射(即通過窗口句柄就能找到窗口對象的地址),找到了窗口對象就可以把消息處理的任務(wù)交給CWnd::WindowProc了(調(diào)用pWnd- >WindowProc)。
下面就是消息映射了 其實這就簡單了,因為這時只需關(guān)注CWnd::WindowProc和消處理函數(shù)(如onCreate)了。在MFC中定義了幾個宏:DECLARE_MESSAGE_MAP、BEGIN_MESSAGE_MAP、END_MESSAGE_MAP等,其實把這幾個宏換回來就很好理解了。為了便于理解,我把這些宏簡化一下: // typedef struct _MSGMAP_ENTRY { UINT nMessage; //消息 void (CWnd::*pfn)(); //消息處理函數(shù)據(jù) }MSGMAP_ENTRY;
DECLARE_MESSAGE_MAP相當于 static MSGMAP_ENTRY _MessageEntry[]; //定義了一個映射表
BEGIN_MESSAGE_MAP、END_MESSAGE_MAP和兩者之間的宏相當于 MSGMAP_ENTRY CWnd::_MessageEntry[] = { {WM_CREATE, &onCreate}, //第一個消息映射 {WM_CLOSE, &onClose}, //第二個消息映射 {0, 0} //消息映射結(jié)尾 };
CWnd::WindowProc之不過是在_MessageEntry[]查找有沒有定義的消息,如有,則調(diào)用相應(yīng)的處理函數(shù),如沒有則調(diào)用CWnd::DefWindowProc
還想提一下Delphi中的相關(guān)處理,Delphi是不是用了同樣的方法呢?答案是否定的,Delphi用匯編語句把類的非靜態(tài)成員函數(shù)的地址賦給lpfnWndProc,這個也很有意思,當然用C++也可這么做。
對于傳遞函做個解釋如下:
AfxWndProc() 該函數(shù)負責接收消息,找到消息所屬的CWnd對象,然后調(diào)用AfxCallWndProc
AfxCallWndProc() 該函數(shù)負責保存消息(保存的內(nèi)容主要是消息標識符和消息參數(shù))供應(yīng)用程序以后使用, 然后調(diào)用WindowProc()函數(shù)
WindowProc() 該函數(shù)負責發(fā)送消息到OnWndMsg()函數(shù),如果未被處理,則調(diào)用DefWindowProc()函數(shù)
OnWndMsg() 該函數(shù)的功能首先按字節(jié)對消息進行排序, 對于WM_COMMAND消息,調(diào)用OnCommand()消息響應(yīng)函數(shù), 對于WM_NOTIFY消息調(diào)用OnNotify()消息響應(yīng)函數(shù)。 任何被遺漏的消息將是一個窗口消息。 OnWndMsg()函數(shù)搜索類的消息映像,以找到一個能處理任何窗口消息的處理函數(shù)。 如果OnWndMsg()函數(shù)不能找到這樣的處理函數(shù)的話,則把消息返回到WindowProc()函數(shù), 由它將消息發(fā)送給DefWindowProc()函數(shù)
OnCommand() 該函數(shù)查看這是不是一個控件通知 (lParam參數(shù)不為NULL,如果lParam參數(shù)為空的話,說明該消息不是控件通知), 如果它是,OnCommand()函數(shù)會試圖將消息映射到制造通知的控件; 如果他不是一個控件通知(或者如果控件拒絕映射的消息)OnCommand()就會調(diào)用OnCmdMsg()函數(shù)
OnNotify()也試圖將消息映射到制造通知的控件; 如果映射不成功,OnNotify()就調(diào)用相同的OnCmdMsg()函數(shù)
OnCmdMsg() 根據(jù)接收消息的類, OnCmdMsg()函數(shù)將在一個稱為命令傳遞(Command Routing)的過程中潛在的傳遞命令消息和控件通知。 例如:如果擁有該窗口的類是一個框架類, 則命令和通知消息也被傳遞到視圖和文檔類,并為該類尋找一個消息處理函數(shù)
|