ComboBox控件-转
轉至http://zaobird.blogdriver.com/zaobird/
WTL for MFC Programming實踐篇
???? ????????????? --- 一個自定義ComboBox的移植過程
?????????????????? --- 蝸牛手記
?
???? 現在有一個MFC寫的自定義ComboBox打算移植到WTL上,于是根據WTL的書寫方法修改了程序,就得到下面的代碼:
Class CComboBoxEx : public CComboBox
{
protected:
???? void OnDrawItem(UINT wParam, LPDRAWITEMSTRUCT lpDrawItemStruct);
public:
???? BEGIN_MSG_MAP_EX(CComboBoxEx)
???????? MSG_OCM_DRAWITEM(OnDrawItem)
???? END_MSG_MAP()
}
?
Class CMainDlg : public CDialogImpl< CMainDlg >
{
Protected:
???????? CComboBoxEx??????? m_cmbEx;
Public:
???? BEGIN_DDX_MAP(CPageConfigFont)
???????? DDX_CONTROL_HANDLE(IDC_COMBOBOXEX, m_cmbEx);
???? END_DDX_MAP()
???? ???? BEGIN_MSG_MAP_EX(CPageConfigFont)
???? ???????? MSG_WM_INITDIALOG(OnInitDialog)
???? ???????? REFLECT_NOTIFICATIONS()
???????? END_MSG_MAP()
}
?
如何生成以上代碼及代碼的含義,原書都有介紹,由于不是本文的重點,不再一一解釋。
要說的是,在WTL 7.1中添加了DDX_CONTROL_HANDLE宏,可以用來設置控件,與DDX_CONTROL不同的是,它不要求控件類由CWindowImpl派生,即不需要包含SubclassWindow()函數,這樣我們才可以使用DDX來設置我們從CComboBox派生的類(聽上去很有道理,其實卻是在MFC編程習慣帶動下錯誤思維)。
當然,要實現還有一個小小的問題,DDX_CONTROL_HANDLE宏需要我們的類包含一個操作符“=”,怎么寫這個函數呢?參看了一下基類的實現方法:
???? CComboBoxExT< TBase >& operator =(HWND hWnd)
???? {
???????? m_hWnd = hWnd;
???????? return *this;
???? }
參看WTL文件<atlctrls.h>
原來只是將m_hWnd賦值,于是我們在我們的類中添加如下的代碼:
CComboBoxEx& operator=(HWND hWnd)
{
???? m_hWnd = hWnd;
???? return *this;
}
于是編譯通過了。(殊不知潛在的錯誤就這樣被深深的埋起來了)
可是為什么DDX_CONTROL_HANDLE宏需要我們的類包含操作符“=”呢?我們來看看DDX_CONTROL_HANDLE宏是怎么實現的:
整個DDX_MAP其實是定義了一個DoDataExchange函數,BEGIN_DDX_MAP宏定義了函數頭,而END_DDX_MAP定義了函數尾,中間一項項的DDX定義函數的具體內容,而當你在代碼中定義DDX_MAP的時候就等于重載了CWinDataExchange::DoDataExchange()函數,具體代碼如下:
#define BEGIN_DDX_MAP(thisClass) \
???? BOOL DoDataExchange(BOOL bSaveAndValidate = FALSE, UINT nCtlID = (UINT)-1) \
???? { \
???????? bSaveAndValidate; \
???????? nCtlID;
#define END_DDX_MAP() \
???????? return TRUE; \
???? }
參看WTL文件<atlddx.h>
對于DDX_CONTROL_HANDLE宏,它其實是調用了CWinDataExchange:: DDX_Control_Handle函數,具體代碼如下:
// Simple control attaching (for HWND wrapper controls)
???? template <class TControl>
???? void DDX_Control_Handle(UINT nID, TControl& ctrl, BOOL bSave)
???? {
???????? if(!bSave && ctrl.m_hWnd == NULL)
???????? {
????????????? T* pT = static_cast<T*>(this);
????????????? ctrl = pT->GetDlgItem(nID);
???????? }
???? }
參看WTL文件<atlddx.h>
正如上面的代碼,DDX_CONTROL_HANDLE宏是直接將ID所對應的窗體句柄直接賦值給DDX所鏈接的控件類,于是我們在DDX_MAP中定義的語句與下面的語句是等價的:
m_cmbEx = this->GetDlgItem(IDC_COMBOBOXEX);
所以要想使上面的語句能夠使用,重載操作符就變成了一個解決問題的好辦法,這就是DDX_CONTROL_HANDLE宏需要我們的類包含操作符“=”的原因。
到這里,我們已經知道了為什么,也作了應該做的事,移植的工作就剩下測試了。當然如果你熟悉WTL或者仔細看了上面的代碼,也許會發現有一個很大的問題潛伏著。可是我們是MFC的程序員,習慣用MFC的方法去思考,于是奇怪的事情在測試的時候發生了。
運行一切正常,只是我們在重畫函數中的代碼沒有運行,換句話說,就是重畫事件沒有被觸發。
為什么?我們的所有代碼都是按照正確的方法寫成的,在CComboBoxEx的MSG_MAP中添加MSG_OCM_DRAWITEM宏來映射重畫事件,在CMainDlg的MSG_MAP中添加REFLECT_NOTIFICATIONS()宏。
該做得都做了。為什么不行呢?
在原書中提到,使用 DEFAULT_REFLECTION_HANDLER來處理缺省的反射事件,難道因為缺少這個宏嗎?雖然這不是一個符合邏輯的想法,可是現在也把它拿來當活馬醫一醫了。
于是我們在原來的類中添加這個宏,結果錯誤出現了,提示沒有DefaultReflectionHandler函數的定義,哦?這是什么意思啊?我們來查查原碼:
#define DEFAULT_REFLECTION_HANDLER() \
???? if(DefaultReflectionHandler(hWnd, uMsg, wParam, lParam, lResult)) \
???????? return TRUE;
參看ATL文件<atlwin.h>
原來DEFAULT_REFLECTION_HANDLER宏只是調用DefaultReflectionHandler函數,那么這個函數又是何許人也呢?DefaultReflectionHandler是CWindowImplRoot的成員函數,也可以說是CWindowImpl的成員函數,因為CWindowImpl由CWindowImplBase派生,而CWindowImplBase由CWindowImplRoot派生,DefaultReflectionHandler函數其實是對API函數DefWindowProc的封裝,不過它只限于處理OCM_的事件。如下面的代碼:
template <class TBase>
BOOL CWindowImplRoot< TBase >::DefaultReflectionHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult)
{
???? switch(uMsg)
???? {
???? case OCM_COMMAND:
???? case OCM_NOTIFY:
???? case OCM_PARENTNOTIFY:
???? case OCM_DRAWITEM:
???? case OCM_MEASUREITEM:
???? case OCM_COMPAREITEM:
???? case OCM_DELETEITEM:
???? case OCM_VKEYTOITEM:
???? case OCM_CHARTOITEM:
???? case OCM_HSCROLL:
???? case OCM_VSCROLL:
???? case OCM_CTLCOLORBTN:
???? case OCM_CTLCOLORDLG:
???? case OCM_CTLCOLOREDIT:
???? case OCM_CTLCOLORLISTBOX:
???? case OCM_CTLCOLORMSGBOX:
???? case OCM_CTLCOLORSCROLLBAR:
???? case OCM_CTLCOLORSTATIC:
???????? lResult = ::DefWindowProc(hWnd, uMsg - OCM__BASE, wParam, lParam);
???????? return TRUE;
???? default:
???????? break;
???? }
???? return FALSE;
}
看到這里,如果想添加DEFAULT_REFLECTION_HANDLER宏,控件類就要由CWindowImpl派生。為了測試把死馬當活馬醫的想法,我們把類的定義改為如下這樣:
class CComboBoxEx:public CWindowImpl< CComboBoxEx, CComboBox>
于是,添加DEFAULT_REFLECTION_HANDLER宏得操作通過了編譯,但是事實證明,不合邏輯的想法很難帶來正確的結果,不僅重畫事件沒有被觸發,修改后,在控件類析構時碰到了ATL的斷言。
錯誤提示是,類在窗體句柄銷毀之前被析構。
這個錯誤到讓我們想到原書中提到的一個WTL特性,WTL不會自動銷毀窗體句柄,需要自己手工Detach()窗體句柄。既然這樣,我們又添加了下面的代碼:
~CComboBoxEx() {
???? Detach();
}
雖然,沒有Attach()的Detach()感覺有點怪,可是畢竟ATL的斷言不會出現了。但是,問題并沒有解決,重畫事件還是沒有被觸發。難道是CMainDlg沒有反射事件回來?看看用來反射事件的REFLECT_NOTIFICATIONS宏的代碼:
#define REFLECT_NOTIFICATIONS() \
???? { \
???????? bHandled = TRUE; \
???????? lResult = ReflectNotifications(uMsg, wParam, lParam, bHandled); \
???????? if(bHandled) \
????????????? return TRUE; \
???? }
????????????? 參看ATL文件<atlwin.h>
REFLECT_NOTIFICATIONS宏調用的是函數CWindowImplRoot::ReflectNotifications。這個函數通過參數取得發送事件控件的窗體句柄,并通過該句柄將事件發還給控件,代碼如下:
template <class TBase>
LRESULT CWindowImplRoot< TBase >::ReflectNotifications(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
???? HWND hWndChild = NULL;
?
???? switch(uMsg)
???? {
???? case WM_COMMAND:
???????? if(lParam != NULL) // not from a menu
????????????? hWndChild = (HWND)lParam;
???????? break;
???? case WM_NOTIFY:
???????? hWndChild = ((LPNMHDR)lParam)->hwndFrom;
???????? break;
???? case WM_PARENTNOTIFY:
???????? switch(LOWORD(wParam))
???????? {
???????? case WM_CREATE:
???????? case WM_DESTROY:
???????? ???? hWndChild = (HWND)lParam;
????????????? break;
???????? default:
????????????? hWndChild = GetDlgItem(HIWORD(wParam));
????????????? break;
???????? }
???????? break;
???? case WM_DRAWITEM:
???????? if(wParam)??? // not from a menu
????????????? hWndChild = ((LPDRAWITEMSTRUCT)lParam)->hwndItem;
???????? break;
???? case WM_MEASUREITEM:
???????? if(wParam)??? // not from a menu
????????????? hWndChild = GetDlgItem(((LPMEASUREITEMSTRUCT)lParam)->CtlID);
???????? break;
???? case WM_COMPAREITEM:
???????? if(wParam)??? // not from a menu
????????????? hWndChild = GetDlgItem(((LPCOMPAREITEMSTRUCT)lParam)->CtlID);
???????? break;
???? case WM_DELETEITEM:
???????? if(wParam)??? // not from a menu
????????????? hWndChild = GetDlgItem(((LPDELETEITEMSTRUCT)lParam)->CtlID);
???????? break;
???? case WM_VKEYTOITEM:
???? case WM_CHARTOITEM:
???? case WM_HSCROLL:
???? case WM_VSCROLL:
???????? hWndChild = (HWND)lParam;
???????? break;
???? case WM_CTLCOLORBTN:
???? case WM_CTLCOLORDLG:
???? case WM_CTLCOLOREDIT:
???? case WM_CTLCOLORLISTBOX:
???? case WM_CTLCOLORMSGBOX:
???? case WM_CTLCOLORSCROLLBAR:
???? case WM_CTLCOLORSTATIC:
???????? hWndChild = (HWND)lParam;
???????? break;
???? default:
???????? break;
???? }
?
???? if(hWndChild == NULL)
???? {
???????? bHandled = FALSE;
???????? return 1;
???? }
?
???? ATLASSERT(::IsWindow(hWndChild));
???? return ::SendMessage(hWndChild, OCM__BASE + uMsg, wParam, lParam);
}
????????????? 參看ATL文件<atlwin.h>
???? 我們感興趣的是最后一句,控件接收到的是ID = OCM__BASE + WM_DRAWITEM的消息,那么我們可以讓控件直接接收消息(OCM__BASE + WM_DRAWITEM),用于取代使用不起作用的MSG_OCM_DRAWITEM。于是有了下面的代碼:
???? MESSAGE_HANDLER_EX(OCM__BASE + WM_DRAWITEM, OnDrawItem)
???? 但是結果還是一樣 - 重畫事件沒有被觸發。
幸虧我們有了新的發現,否則有可能就沒由信心解決這個問題了。我們在CMainDlg中添加了WM_DRAWITEM事件,結果捕抓到了CComboBoxEx的重畫事件,這說明CComBoxEx的重畫事件發出了,但不知什么原因沒有反射回控件。于是我們在CMainDlg::OnDrawItem()中添加了
SendMessage(m_cmbEx.m_hWnd, OCM__BASE + WM_DRAWITEM, 0, 0)
以取代REFLECT_NOTIFICATIONS宏所做的自動反射,結果發現,事件還是沒有收到。難道WTL事件處理出了問題?我們又為CComboBoxEx添加了非反射的事件WM_PAINT,結果發現WM_PAINT事件也沒有被觸發!!!
CComboBoxEx根本無法收到任何事件!!!!!
WTL for MFC Programming實踐篇 --- 一個自定義ComboBox的移植過程(下)- -??????????????????????????????????????
《程序員修煉之道》說當你想說這不可能的時候,往往是你在調用的方法上出現了錯誤。
我們重新回到起點,來看看那里出了錯。仔細地研讀代碼以后發現,事件是怎么傳遞到MSG_MAP的呢?難道我們通過賦值將一個窗體句柄傳進來,我們在這個類中定義的MSG_MAP就能自動的連接到這個句柄上嗎?這顯然是真的不可能。
那么沒有將MSG_MAP連接到窗體句柄很可能是控件類無法收到任何事件的原因。那么如何將MSG_MAP連接到窗體句柄上呢?原書中提到一個重要的函數,CWindowImpl::SubclassWindow()。我們再次更改我們的控件類:
???? CComboBoxEx& operator =(HWND hWnd) {
???????? CWindowImpl< CComboBoxEx, CComboBox>::SubclassWindow(hWnd);
???????? return *this;
???? }
???? 一測之下,大吃一驚。不僅重畫事件被正確觸發,連析構函數中的沒有Attach的Detach這個怪用法也可以刪除了。為什么會這樣呢?探究這個問題之前,讓我們先看看原書使用的DDX_CONTROL宏 - 它只針對CWindowImpl的派生類起作用 - 是怎么回事。原碼如下:
???? #define DDX_CONTROL(nID, obj) \
???????? if(nCtlID == (UINT)-1 || nCtlID == nID) \
????????????? DDX_Control(nID, obj, bSaveAndValidate);
?
???? // Full control subclassing (for CWindowImpl derived controls)
template <class TControl>
???? void DDX_Control(UINT nID, TControl& ctrl, BOOL bSave)
???? {
???????? if(!bSave && ctrl.m_hWnd == NULL)
???????? {
????????????? T* pT = static_cast<T*>(this);
????????????? ctrl.SubclassWindow(pT->GetDlgItem(nID));
???????? }
???? }
???? 從原碼可以看到,DDX_CONTROL宏和DDX_CONTROL_HANDLER宏實現的區別只是,前者使用SubclassWindow,而后者使用操作符“=”。如果把我們上面的代碼聯系起來,在操作符“=”的處理函數中調用SubclassWindow,其實就等于是明著使用DDX_CONTROL_HANDLER宏,暗地里卻把DDX_CONTROL宏實現了。原來想出門,結果先繞著后院跑了3圈,這真是一個大笑話。
???? 為什么會這樣呢?不使用DDX_CONTROL宏是因為CComboBox沒有SubclassWindow函數,而是用CComboBox是因為在MFC中CComboBoxEx就是從CComboBox派生,移植的時候當然傾向于選擇同名的類,而不是CWindowImpl<CComboBoxEx, CComboBox>這樣怪怪的聲明方法。
???? 可是這里忽視了一個基本的WTL特性,由于WTL基于ATL,而設計ATL就是為了將接口和實現分開,所以在WTL中所有不帶Impl字樣的類都不是實現類,像CWindow,CButton,CComboBox等等,他們只是包含一個句柄,沒有自己的事件,他們只是負責中轉,封裝控件事件等等。像CComboBox的操作符“=”就只是一個賦值語句而已。而DDX_CONTROL_HANDLER正是為這些類服務的,當然如果我們注意到這個宏得注釋,也許早就發現這個問題了,還記得嗎?在這里重溫一下吧:
???? // Full control subclassing (for CWindowImpl derived controls)
template <class TControl>
???? void DDX_Control(UINT nID, TControl& ctrl, BOOL bSave)
?
// Simple control attaching (for HWND wrapper controls)
???? template <class TControl>
???? void DDX_Control_Handle(UINT nID, TControl& ctrl, BOOL bSave)
?
???? 好了,在環游地球一周以后,我們又回到了起點,雖然費了不少的力氣,但也搞清楚不少的東西,下面大概地總結一下:
1.????? WTL的類包含接口類(只包含窗體句柄和事件的封裝)和實現類(可以擁有自己的事件),要根據具體情況有選擇的使用。
2.????? WTL不會自動銷毀窗體句柄(當然是指接口類),所以Attach操作以后要記著Detach
3.????? 注意包含有HANDLE的宏,類,函數,它們往往是接口類或為接口類服務的,如上面所說的DDX_Control_Handle,以及CDCHandle等等。
4.????? DDX是通過宏定義重載CWinDataExchange::DoDataExchange()函數實現的
5.????? 消息反射是在取道發送消息的窗體句柄后,通過像它回發相應的消息來實現的。
6.????? 當你想說這不可能的時候,往往是你在調用的方法上出現了錯誤。
7.????? 多看看代碼,你會了解得更多。
轉載于:https://www.cnblogs.com/paopao/archive/2006/07/10/447360.html
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的ComboBox控件-转的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 制作WEB在线编辑器-插入HTML标签
- 下一篇: 接口编程(一)