WM_DRAWITEM與DrawItem()的討論 我在學習中經(jīng)常遇到要重寫DrawItem()的情況,,但又有一個WM_DRAWITEM消息,它們是什么樣的關(guān)系呢,。 如果我們要重寫一個CButton取名為CMyButton,,我們可以重寫CMyButton的DrawItem()函數(shù)來實現(xiàn)我們的 需求,但CMyButton::DrawItem()是在什么時候調(diào)用呢,?它是在它的宿主類的OnDrawItem()中被調(diào)用,, OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct )正是對WM_DRAWiTEM的相應函數(shù),。 宿主類可以根據(jù)nIDCtl來判定是哪個子控件。其實我們可以在OnDrawItem函數(shù)里對子控件進行繪制,,但是有很多 的子控件看起來不好,,所以我們應該在子類的DrawItem對子類繪制,例如CMyButton::DrawItem,。所以可以 這樣理解,,OnDrawItem是畫窗口中的子控件的,因為它的入口參數(shù)LPDRAWITEMSTRUCT帶入不同子控件的相 關(guān)參數(shù),,而且,,你得把字控件設(shè)置成“自畫”類型,才會調(diào)用到OnDrawItem,。 當自繪按鈕(owner-draw button),,下拉列表框(combo box),列表框(list box)視覺屬性,,或者菜單發(fā)生變化時,, 框架為他們的owner調(diào)用OnDrawItem(發(fā)送WM_DRAWITEM),在宿主類調(diào)用子類的DrawItem(發(fā)送WM_DRAWITEM消息),。 我們可以重載子類的DrawItem可以繪制自己需要的控件,,不是所有設(shè)置成自畫類型的控件都會調(diào)用父窗口的OnDrawItem, 例如ListBox的自畫,,你就必須重載CListBox的DrawItem方法和MeasureItem方法才可以,,但象菜單,按鈕等的自畫則會調(diào)用 OnDrawItem,。在SDK中,,子類是不可能受到WM_DRAWITEM,在MFC中可以,,這是類的設(shè)計者設(shè)計的(反射),,這的確不錯。 在學習中還有一個消息也是由宿主類被調(diào)用的,,它就是WM_CTRCOLOR,。這個消息是在子控件將要繪畫時,向宿主 類發(fā)送,,宿主類利用發(fā)射機制讓子類自己又一個處理的機會,。OnCtlColor (CDC* pDC, CWnd* pWnd, UINT nCtlColor) pDC,pWnd都是于子類相關(guān)的,在這里可以設(shè)置,,前景顏色,,背景顏色,畫刷類型,字體等等,,但不能改變元素的界面框架,, 這是DrawItem 所能干的。 如果同時有DrawItem(子類),,OnDrawItem(宿主類),,OnCtlColor(宿主類),它們的調(diào)用順序是: OnCtlColor,,OnDrawItem,,DrawItem。 如果我們同時又相應的子類的WM_PAINT消息,,這也許OnPaint在內(nèi)部進行了一些處理,,判斷是否自繪來決定是否向宿主類 發(fā)送WM_DRAWITEM,所以如果響應了WM_PAINT子類就不會向宿主類發(fā)送WM_DRAWITEM消息,,你要完成子類的全部繪 制工作,,如果子類是一個列表框,就很麻煩,。這時調(diào)用順序是OnCtlColor,OnPaint。 在發(fā)送一個WM_PAINT消息前,,總會先發(fā)送一個WM_ERASEBACK消息,,我們在這里在一個背景圖片。 對于我們平時對控件的繪制,,上面介紹的差不多了,,還有一個CView的問題,也就是OnPaint和Ondraw的關(guān)系,, 其實這個很簡單,,CView::OnPaint()的源碼如下: - void CView::OnPaint()
- {
- CPaintDC dc(this);
- OnPrepareDC(&dc);
- OnDraw(&dc)
- }
從代碼中可以清楚的看出他們的關(guān)系。
MFC中OnDraw與OnPaint的區(qū)別 OnPaint是WM_PAINT消息的消息處理函數(shù),,在OnPaint中調(diào)用OnDraw,,一般來說,用戶自己的繪圖代碼應放在OnDraw中,。
OnPaint()是CWnd的類成員,,負責響應WM_PAINT消息。OnDraw()是CVIEW的成員函數(shù),,沒有響應消息的功能.當視圖變得無效時(包括大小的改變,,移動,被遮蓋等等),,Windows發(fā)送WM_PAINT消息,。該視圖的OnPaint 處理函數(shù)通過創(chuàng)建CPaintDC類的DC對象來響應該消息并調(diào)用視圖的OnDraw成員函數(shù).OnPaint最后也要調(diào)用OnDraw,因此一般在OnDraw函數(shù)中進行繪制。
The WM_PAINT message is sent when the UpdateWindow or RedrawWindow member function is called.
在OnPaint中,將調(diào)用BeginPaint,,用來獲得客戶區(qū)的顯示設(shè)備環(huán)境,,并以此調(diào)用GDI函數(shù)執(zhí)行繪圖操作。在繪圖操作完成后,,將調(diào)用EndPaint以釋放顯示設(shè)備環(huán)境,。而OnDraw在BeginPaint與EndPaint間被調(diào)用。
1) 在mfc結(jié)構(gòu)里OnPaint是CWnd的成員函數(shù). OnDraw是CView的成員函數(shù). 2) OnPaint()調(diào)用OnDraw(),,OnPrint也會調(diào)用OnDraw(),,所以O(shè)nDraw()是顯示和打印的共同操作。
OnPaint是WM_PAINT消息引發(fā)的重繪消息處理函數(shù),,在OnPaint中會調(diào)用OnDraw來進行繪圖,。OnPaint中首先構(gòu)造一個CPaintDC類得實例,然后一這個實例為參數(shù)來調(diào)用虛函數(shù)OnPrepareDC來進行一些繪制前的一些處理,,比設(shè)置映射模式,,最后調(diào)用OnDraw。而OnDraw和OnPrepareDC不是消息處理函數(shù),。所以在不是因為重繪消息所引發(fā)的OnPaint導致OnDraw被調(diào)用時,,比如在OnLButtonDown等消息處理函數(shù)中繪圖時,要先自己調(diào)用OnPrepareDC,。 至于CPaintDC和CClientDC根本是兩回事情 CPaintDC是一個設(shè)備環(huán)境類,,在OnPaint中作為參數(shù)傳遞給OnPrepareDC來作設(shè)備環(huán)境的設(shè)置。真正和CClientDC具有可比性的是CWindowDC,,他們一個是描述客戶區(qū)域,,一個是描述整個屏幕。 如果是對CVIEW或從CVIEW類派生的窗口繪圖時應該用OnDraw,。
OnDraw()和OnPaint()有什么區(qū)別呢,? 首先:我們先要明確CView類派生自CWnd類。而OnPaint()是CWnd的類成員,,同時負責響應WM_PAINT消息,。OnDraw()是CVIEW的成員函數(shù),并且沒有響應消息的功能,。這就是為什么你用VC成的程序代碼時,,在視圖類只有OnDraw沒有OnPaint的原因。而在基于對話框的程序中,,只有OnPaint,。 其次:我們在第《每天跟我學MFC》3的開始部分已經(jīng)說到了。要想在屏幕上繪圖或顯示圖形,,首先需要建立設(shè)備環(huán)境DC,。其實DC是一個數(shù)據(jù)結(jié)構(gòu),,它包含輸出設(shè)備(不單指你17寸的純屏顯示器,還包括打印機之類的輸出設(shè)備)的繪圖屬性的描述,。MFC提供了CPaintDC類和CWindwoDC類來實時的響應,,而CPaintDC支持重畫。當視圖變得無效時(包括大小的改變,,移動,,被遮蓋等等),Windows 將 WM_PAINT 消息發(fā)送給它,。該視圖的OnPaint 處理函數(shù)通過創(chuàng)建 CPaintDC 類的DC對象來響應該消息并調(diào)用視圖的 OnDraw 成員函數(shù),。通常我們不必編寫重寫的 OnPaint 處理成員函數(shù)。 ///CView默認的標準的重畫函數(shù) void CView::OnPaint() //見VIEWCORE.CPP {
CPaintDC dc(this); OnPrepareDC(&dc),; OnDraw(&dc); //調(diào)用了OnDraw } ///CView默認的標準的OnPrint函數(shù) void CView::OnPrint(CDC* pDC, CPrintInfo*) { ASSERT_VALID(pDC); OnDraw(pDC); // Call Draw }
既然OnPaint最后也要調(diào)用OnDraw,因此我們一般會在OnDraw函數(shù)中進行繪制,。下面是一個典型的程序。 ///視圖中的繪圖代碼首先檢索指向文檔的指針,,然后通過DC進行繪圖調(diào)用,。 void CMyView::OnDraw( CDC* pDC ) {
CMyDoc* pDoc = GetDocument(); CString s = pDoc->GetData(); GetClientRect( &rect ); // Returns a CString CRect rect; pDC->SetTextAlign( TA_BASELINE | TA_CENTER ); pDC->TextOut( rect.right / 2, rect.bottom / 2, s, s.GetLength() ); } 最后:現(xiàn)在大家明白這哥倆之間的關(guān)系了吧。因此我們一般用OnPaint維護窗口的客戶區(qū)(例如我們的窗口客戶區(qū)加一個背景圖片),,用OnDraw維護視圖的客戶區(qū)(例如我們通過鼠標在視圖中畫圖),。當然你也可以不按照上面規(guī)律來,只要達到目的并且沒有問題,,怎么干都成,。補充:我們還可以利用Invalidate(),ValidateRgn(),ValidateRect()函數(shù)強制的重畫窗口,具體的請參考MSDN吧,。
OnDraw中可以繪制用戶區(qū)域。OnPaint中只是當窗口無效時重繪不會保留CClientDC繪制的內(nèi)容,。
這兩個函數(shù)有區(qū)別也有聯(lián)系:
1,、區(qū)別:OnDraw是一個純虛函數(shù),定義為virtual void OnDraw( CDC* pDC ) = 0; 而OnPaint是一個消息響應函數(shù),,它響應了WM_PANIT消息,,也是是窗口重繪消息。
2,、聯(lián)系:我們一般在視類中作圖的時候,,往往不直接響應WM_PANIT消息,而是重載OnDraw純虛函數(shù),,這是因為在CVIEW類中的WM_PANIT消息響應函數(shù)中調(diào)用了OnDraw函數(shù),,如果在CMYVIEW類中響應了WM_PAINT消息,不顯式地調(diào)用OnDraw函數(shù)的話,,是不會在窗口重繪的時候調(diào)用OnDraw函數(shù)的,。
應用程序中幾乎所有的繪圖都在視圖的 OnDraw 成員函數(shù)中發(fā)生,必須在視圖類中重寫該成員函數(shù)。(鼠標繪圖是個特例,,這在通過視圖解釋用戶輸入中討論,。)
OnDraw 重寫: 通過調(diào)用您提供的文檔成員函數(shù)獲取數(shù)據(jù)。 通過調(diào)用框架傳遞給 OnDraw 的設(shè)備上下文對象的成員函數(shù)來顯示數(shù)據(jù),。 當文檔的數(shù)據(jù)以某種方式更改后,,必須重繪視圖以反映該更改。默認的 OnUpdate 實現(xiàn)使視圖的整個工作區(qū)無效,。當視圖變得無效時,,Windows 將 WM_PAINT 消息發(fā)送給它。該視圖的 OnPaint 處理函數(shù)通過創(chuàng)建 CPaintDC 類的設(shè)備上下文對象來響應該消息并調(diào)用視圖的 OnDraw 成員函數(shù),。
當沒有添加WM_PAINT消息處理時,窗口重繪時,由OnDraw來進行消息響應...當添加WM_PAINT消息處理時,窗口重繪時,WM_PAINT消息被投遞,由OnPaint來進行消息響應.這時就不能隱式調(diào)用OnDraw了.必須顯式調(diào)用( CDC *pDC=GetDC(); OnDraw(pDC); ).. 隱式調(diào)用:當由OnPaint來進行消息響應時,系統(tǒng)自動調(diào)用CView::OnDraw(&pDC).
想象一下,,窗口顯示的內(nèi)容和打印的內(nèi)容是差不多的,所以,,一般情況下,,統(tǒng)一由OnDraw來畫。窗口前景需要刷新時,,系統(tǒng)會會調(diào)用到OnPaint,,而OnPaint一般情況下是對DC作一些初始化操作后,調(diào)用OnDraw(),。
OnEraseBkGnd(),,是窗口背景需要刷新時由系統(tǒng)調(diào)用的。明顯的一個例子是設(shè)置窗口的背景顏色(你可以把這放在OnPaint中去做,,但是會使產(chǎn)生閃爍的現(xiàn)象),。 至于怎么界定背景和前景,那要具體問題具體分析了,,一般情況下,,你還是很容易區(qū)別的吧。
的確,,OnPaint()用來響應WM_PAINT消息,,視類的OnPaint()內(nèi)部根據(jù)是打印還是屏幕繪制分別以不同的參數(shù)調(diào)用OnDraw()虛函數(shù)。所以在OnDraw()里你可以區(qū)別對待打印和屏幕繪制,。 其實,,MFC在進行打印前后還做了很多工作,調(diào)用了很多虛函數(shù),,比如OnPreparePrint()等,。
消息反射機制 深度剖析消息反射機制 作者:hustli 摘要:在前面我們分析了控件通知消息WM_NOTIFY,和WM_NOTIFY緊密聯(lián)系的還有一個MFC新特性:消息反射,。本文中,,我想就這個問題作一個全面的論述,,如果有錯誤,還望各路大蝦批評指正,。 什么是消息反射,? 在windows里面,子控件經(jīng)常向父控件發(fā)送消息,,例如很多子控件要繪制自己的背景,,就可能向父窗口發(fā)送消息WM_CTLCOLOR。對于從子控件發(fā)來的消息,,父控件有可能在處理之前,,把消息返還給子控件處理,這樣消息看起來就想是從父窗口反射回來一樣,,故此得名:消息反射,。 消息反射的由來 在windows和MFC4.0版本一下,父窗口(通常是一個對話框)會對這些消息進行處理,,換句話說,,自控件的這些消息處理必須在父窗口類體內(nèi),每當我們添加子控件的時候,,就要在父窗口類中復制這些代碼,,我們可以想象這是多么的復雜,代碼是多么的臃腫,! 我們可以想象,,如果這些消息都讓父窗口類去做,父窗口就成了一個萬能的神,,一個臃腫不堪的代碼機,,無論如何消息的處理都集中在父窗口類中,會使父窗口繁重無比,,但是子控件卻無事可做,,并且代碼也無法重用,這對于一個程序員來講是多么痛苦的一件事,?! 在老版本的MFC中,,設(shè)計者也意識到了這個問題,,他們對一些消息采用了虛擬機制,例如:WM_DRAWITEM,,這樣子控件就有機會控制自己的動作,,代碼的可重用性有了一定的提高,但是這還沒有達到大部分人的要求,,所以在高版本的MFC中,,提出了一種更方便的機制:消息反射,。 通過消息反射機制,子控件窗口便能夠自行處理與自身相關(guān)的一些消息,,增強了封裝性,,同時也提高了子控件窗口類的可重用性。不過需要注意的是:消息反射是MFC實現(xiàn)的,,不是windows實現(xiàn)的,;要讓你的消息反射機制工作,你得類必須從CWnd類派生,。 Message-Map中的處理 如果想要處理消息反射,,必須了解相應的Message-Map宏和函數(shù)原型。一般來講,,Message-Map是有一定的規(guī)律的,,通常她在消息的前面加上一個ON_ ,然后再消息的最后加上 _REFLECT,。例如我們前面提到的WM_CTLCOLOR 經(jīng)過處理后變成了ON_WM_CTLCOLOR_REFLECT,;WM_MEASUREITEM則變成了ON_WM_MEASUREITEM_REFLECT。 凡事總會有例外,,這里也是這樣,,這里面有3個例外: (1) WM_COMMAND 轉(zhuǎn)換成 ON_CONTROL_REFLECT; (2) WM_NOTIFY 轉(zhuǎn)換成 ON_NOTIFY_REFLECT,; (3) ON_UPDATE_COMMAND_UI 轉(zhuǎn)換成 ON_UPDATE_COMMAND_UI_REFLECT,; 對于函數(shù)原型,也必須是以 afx_msg 開頭,。 利用ClassWizard添加消息反射 (1)在ClassWizard中,,打開選擇項Message Maps; (2)在下拉列表Class name中選擇你要控制的類,; (3)在Object IDs中,,選中相應的類名; (4)在Messages一欄中找到前面帶有=標記的消息,,那就是反射消息,; (5)雙擊鼠標或者單擊添加按鈕,然后OK! 消息處理的過程 (1)子窗口向父窗口發(fā)送通知消息,,激發(fā)父窗口去調(diào)用它的虛函數(shù)CWnd::OnNotify,。大致的結(jié)構(gòu)如下 BOOL CWnd::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult) { if (ReflectLastMsg(hWndCtrl, pResult)) file://hWndCtrl ,為發(fā)送窗口 return TRUE; file://如果子窗口已處理了此消息,返回 AFX_NOTIFY notify; notify.pResult = pResult; notify.pNMHDR = pNMHDR; return OnCmdMsg(nID, MAKELONG(nCode, WM_NOTIFY)? notify:NULL); } (2)ReflectLastMsg聲明如下:static BOOL PASCAL ReflectLastMsg(HWND hWndChild, LRESULT* pResult = NULL); 它的主要任務就是調(diào)用發(fā)送窗口的SendChildNotifyLastMsg,。 (3)SendChildNotifyLastMsg聲明如下:BOOL SendChildNotifyLastMsg(LRESULT* pResult = NULL); 調(diào)用發(fā)送窗口的虛函數(shù)OnChildNotify函數(shù),,進行處理。 如果發(fā)送窗口沒有進行重載處理,,則調(diào)用ReflectChildNotify(...)函數(shù)進行標準的反射消息的消息映射處理,。 使用的一個例子 這里面我們舉一個簡單的例子,,希望大家能夠更清晰的掌握消息反射機制。 (1)創(chuàng)建一個基于對話框的工程,。 (2)利用向?qū)?chuàng)建一個新的類:CMyEdit,,基類是CEdit。 (3)在CMyEdit頭文件中加入3個成員變量: COLORREF m_clrText ; COLORREF m_clrBkgnd ; CBrush m_brBkgnd; (4)利用向?qū)г谄渲屑尤隬M_CTLCOLOR(看到了么,,前面是不是有一個=,?),并且將它的函數(shù)體改為: HBRUSH CMyEdit::CtlColor(CDC* pDC, UINT nCtlColor) { pDC->SetTextColor( m_clrText ); // text pDC->SetBkColor( m_clrBkgnd ); // text bkgnd return m_brBkgnd; // ctl bkgnd } 同時我們在.cpp文件中會看到ON_WM_CTLCOLOR_REFLECT(),,這就是我們所說的經(jīng)過處理的宏,,是不是很符合規(guī)則? (5)在對話框中加入一個Edit,,增加一個關(guān)聯(lián)的變量,,選擇Control屬性,類別為CMyEdit,。 (6)在對話框.cpp文件中加入#include "MyEdit.h",,運行,看到了什么,?呵呵,。
PreTranslateMessage和TranslateMessage區(qū)別 PreTranslateMessage是消息在送給TranslateMessage函數(shù)之前被調(diào)用的,絕大多數(shù)本窗口的消息都要通過這里,,比較常用,,當需要在MFC之前處理某些消息時,常常要在這里添加代碼.
MFC 消息控制流最具特色的地方是CWnd類的虛擬函數(shù)PreTranslateMessage(),,通過重載這個函數(shù),,可以改變MFC的消息控制流程,甚至可 以作一個全新的控制流出來,。只有穿過消息隊列的消息才受PreTranslateMessage()影響,,采用SendMessage()或其他類似的方 式向窗口直接發(fā)送的而不經(jīng)過消息隊列的消息根本不會理睬PreTranslateMessage()的存在。
是否調(diào)用TranslateMessage()和DispatchMessage()是由一個名稱為PreTranslateMessage()函數(shù)的返回值決定的,,如果該函數(shù)返回TRUE,,則不會把該消息分發(fā)給窗口函數(shù)處理。
傳給PreTranslateMessage()的消息是未經(jīng)翻譯過的消息,,它沒有經(jīng)過TranslateMessage()處理,。可以在該函數(shù)中使用(pMsg->wParam==VK_RETURN)來攔截回車鍵,。wParam中存放的是鍵盤上字符的虛擬碼。
PeekMessage和GetMessage的區(qū)別:
GetMessage在沒有消息的時候等待消息,,cpu當然低
PeekMessage沒有消息的時候立刻返回,,所以cpu占用率高,。
因為游戲不能靠windows消息驅(qū)動,所以要用PeekMessage();
PretranslateMessage 的實現(xiàn),,不得不談到MFC消息循環(huán)的實現(xiàn),。MFC通過CWinApp類中的Pumpmessage函數(shù)實現(xiàn)消息循環(huán),但是實際的消息循環(huán)代碼位于 CWinThread中,,CWinApp只是從CWinThread繼承過來,。其簡化后的代碼大概如下: BOOL CWinThread::PumpMessage() { _AFX_THREAD_STATE *pState = AfxGetThreadState(); ::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL)) if (!AfxPreTranslateMessage(&(pState->m_msgCur))) { ::TranslateMessage(&(pState->m_msgCur)); ::DispatchMessage(&(pState->m_msgCur)); } return TRUE; } 可以看到,PumpMessage在實際的TranslateMessage和DispatchMessage發(fā)生之前會調(diào)用 AfxPreTranslateMessage,,AfxPreTranslateMessage又會調(diào)用 CWnd::WalkPreTranslateTree(雖然也會調(diào)用其他函數(shù),,但是這個最為關(guān)鍵),其代碼如下: BOOL PASCAL CWnd::WalkPreTranslateTree(HWND hWndStop, MSG* pMsg) { ASSERT(hWndStop == NULL || ::IsWindow(hWndStop)); ASSERT(pMsg != NULL); // walk from the target window up to the hWndStop window checking // if any window wants to translate this message for (HWND hWnd = pMsg->hwnd; hWnd != NULL; hWnd = ::GetParent(hWnd)) { CWnd* pWnd = CWnd::FromHandlePermanent(hWnd); if (pWnd != NULL) { // target window is a C window if (pWnd->PreTranslateMessage(pMsg)) return TRUE; // trapped by target window (eg: accelerators) } // got to hWndStop window without interest if (hWnd == hWndStop) break; } return FALSE; // no special processing } 可以看到,,代碼還是很直接的,。從接受到消息的窗口層層往上遍歷,并調(diào)用PretranslateMessage看是否返回TRUE,,是則結(jié)束,,否則繼續(xù)。 這里有一個地方非常關(guān)鍵:CWnd *pWnd = CWnd::FromHandlePermanent(hWnd) 這一句代碼從當前AfxModuleThreadState拿到Permanent句柄表,,從而找到hWnd對應的CWnd
MFC 中PreTranslateMessage是GetMessage(...)函數(shù)的下一級操作,,即GetMessage(...)從消息隊列中獲取消息 后,交由PreTranslateMessage()處理,,若其返回FALSE則再交給TranslateMessage和 DispatchMessage處理(進入WindowProc),; 如果用SendMessage, 則消息直接交到WindowProc處理,所以GetMessage不會取得SendMessage的消息,,當然PreTranslateMessage也就不會被調(diào)用,。 [Page] 如果用PostMessage,則消息進入消息隊列,由GetMessage取得,,PreTranslateMessage就有機會進行處理,。
WindowProc和DefWindowProc的區(qū)別 1. WindowProc是你給自己的窗口定義的窗口處理函數(shù) DefWindowProc是windows平臺提供的默認窗口處理函數(shù) 如果某些消息你不需要做特別的處理,調(diào)用DefWindowProc進行處理就可以了,,不需要你自己再去些那些windows的"標準動作"
2. 根據(jù)1,,顯然,你只能定義WindowProc而不能定義DefWindowProc
看看MFC的CWnd源碼就一目了然了 LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { // OnWndMsg does most of the work, except for DefWindowProc call LRESULT lResult = 0; if (!OnWndMsg(message, wParam, lParam, &lResult)) // 如果消息是需要自己處理的,, //處理后就不必讓windows系統(tǒng)進行默認處理了 lResult = DefWindowProc(message, wParam, lParam); // 自己沒有處理的就交給windows去做
return lResult; }
CWnd中PreCreateWindow,、PreSubclassWindow、SubclassWindow的區(qū)別 http://blog.csdn.net/swimmer2000/archive/2007/10/30/1856213.aspx MFC(VC6.0)的CWnd及其子類中,,有如下三個函數(shù): 讓人很不容易區(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ū)ο笾械呐缮宇悺?/p> 好了,,PreCreateWindow由framework在窗口創(chuàng)建前被調(diào)用,函數(shù)名也說明了這一點,,Pre應該是previous的縮寫,,PreSubclassWindow由framework在subclass窗口前調(diào)用。 這段話說了等于沒說,,你可能還是不知道,,什么時候該改寫哪個函數(shù)。羅羅嗦嗦的作者,,還是用代碼說話吧,!源碼之前,了無秘密(候捷語),。我們就看看MFC中的這三個函數(shù)都是這樣實現(xiàn)的吧,! // From VS Install PathVC98MFCSRCWINCORE.CPP 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ù)。 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的代碼: 其主要作用的::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)用,。見如下代碼: 也在調(diào)用函數(shù)SetWindowLong進行窗口subclass前調(diào)用了PreSubclassWindow.
|