分享
 
 
 

WTL for MFC Programming实践篇 --- 一个自定义ComboBox的移植过程(上)

王朝vc·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

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根本无法收到任何事件!!!!!

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有