日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

ComboBox控件-转

發布時間:2024/6/3 编程问答 48 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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控件-转的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。