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

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的方法去思考,于是奇怪的事情在测试的时候发生了。

运行一切正常,只是我们在重画函数中的代码没有运行,换句话说,就是重画事件没有被触发。

为什么?我们的所有代码都是按照正确的方法写成的,在CComboBoxExMSG_MAP中添加MSG_OCM_DRAWITEM宏来映射重画事件,在CMainDlgMSG_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函数,那么这个函数又是何许人也呢?DefaultReflectionHandlerCWindowImplRoot的成员函数,也可以说是CWindowImpl的成员函数,因为CWindowImplCWindowImplBase派生,而CWindowImplBaseCWindowImplRoot派生,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根本无法收到任何事件!!!!!

阅读终点,创作起航,您可以撰写心得或摘录文章要点写篇博文。去创作
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
下面是网络收集 WTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Part I - ATL GUI Classes - WTL WTL for MFC Programmers, Part II - WTL GUI Base Classes - WTL WTL for MFC Programmers, Part III - Toolbars and Status Bars - WTL WTL for MFC Programmers, Part IV - Dialogs and Controls - WTL WTL for MFC Programmers, Part IX - GDI Classes, Common Dialogs, and Utility Classes - WTL WTL for MFC Programmers, Part V - Advanced Dialog UI Classes - WTL WTL for MFC Programmers, Part VI - Hosting ActiveX Controls - WTL WTL for MFC Programmers, Part VII - Splitter Windows - WTL WTL for MFC Programmers, Part VIII - Property Sheets and Wizard 由于工作的需要经常开发一些COM组件,在要求不能使用MFC的场合就是用ATL。ATL提供了对窗口的面向对象地封装和简单的消息映射机制,但是ATL过于简单,用它开发应用程序几乎不可能。要想让ATL具备界面框架解决方案的功能还需要做很多事情,幸运的是WTL就做了这些事情。WTL是个很奇特的东西,它由微软公司一群热情的程序员维护,它从未出现在微软的官方产品名单上,但可以从微软的官方网站下载最新的WTL。它没有正式的文档支持,用WTL做关键字在MSDN中检索只能得到0个结果,但是全世界的开发网站上都有针对WTL的讨论组和邮件列表,任何问题都会得到热情的解答。我认真地对比了MFCWTL,发现二者有很多相通之处,MFC的功能几乎都能在WTL中实现,只是方法不同而已。我几乎不费吹灰之力就将以前写的一个MFC程序用WTL改写了,使用静态链接的WTL程序比使用动态链接的MFC程序还要小,资源占用只有MFC程序的一半。 但是一时的热情不能解决文档缺乏的困扰,虽然网上有很多使用WTL的例子和说明文章,几乎把MFC能实现的各种稀奇古怪的效果都实现了,但都是着眼于局部问题得解决,缺乏系统地全面地介绍WTL的文章。就在这个时候我看到了迈克尔.敦(Michael Dunn)的“WTL for MFC Programmers”系列文章,我的感觉和1995年我第一次见到MSDN时一样,几乎是迫不及待地将其读完,同时也萌发了将其翻译成汉语的冲动。于是给Michael写了封邮件,希望能够得到授权将他的文章翻译成汉语(事实上在这之前我已经翻译了两章了)。在得到授权确认后才发现这个工作是多么的困难,但为时已晚,只能硬着头皮撑下去。
WTL 在开发者之间的悄悄传播已经超过一年了, 传闻它是基于ATL的,并在微软内部使用.这理所当然的引起了ATL开发者社区的注意.这些人从ATL1.1开始,就一直为ATL控件书写UI代码,但是他们发现,他们的所写的代码常常就是纯的Win32 GDI代码.我告诉您, WTL并没有多大不同.是不是让人失望? 不,因为ATL只是对COM进行了简单的封装,这也是ATL的强大之处. 是的,写ATL您必须通晓COM. 您在ATL上额外花费的功夫跟您学习COM所作的努力比起来,简直微不足道.这跟那些需要把主要精力花费在学习类库本身,忽略COM的库是完全不同的.WTL与此类似.您需要懂得Win32窗口技术和GDI.只要您懂得,学习WTL就似清风抚面,再简单不过了.如果您不懂 这些,那么您最好使用VB来写UI代码. 它给各种类型的应用程序提供了一个基本的框架.注意,虽然您没有MFC那样的文档/视结构,但是您有视(views). 在WTL有大量的代码让您来管理视,而且加入您自己的代码也很容易. WTL有AppWizard,可以让您生成SDI, MDI 和多线程SDI程序多线程SDI跟IE或Windows Explorer很像.它看起来是打开了多个程序实例,实际上这些窗口都是属于一个进程的.另外,您的程序可以是基于对话框的,也可以是基于视的.视可以是基于CWindowImpl的,也可以是基于控件,甚至是IE里的一个HTML页.您可以选择您的程序是否需要一个rebar, command bar (CE-like), toolbar 和/或status bar.另外,您的程序可以主持ActiveX控件,以及成为一个COM服务器. 这里有几个关于视的选项. WTL提供splitter窗口类(这样在一个视里您可以有两个窗口)和scroll窗口类(这样您的窗口可以比它显示的"视"小). WTL也有个类似MFC的UpDateUI的东西,但是它们不是很一样 - 主要的区别是您需要把需要更新的项用宏映射标注出来,然后您在您的类里加入执行UpdateUI的代码. DDX/DDV在WTL也支持,同样类似MFC,但有不同. 您必须加一个宏映射来实现DoDataExchange,然后加入调用它的代码.现在WTL也有GDI类了.然而,HDC的封装类就像CWindow一样,只进行了很简单的封装 - 它几乎没有加入任何新的功能.不过,在WTL,你可以得到播放meta文件和OpenGL支持. 最有价值的我猜应该是打印机DC的那些继承类 - WTL有打印机支持,甚至打印预览. 当然也有GDI对象的封装. 诸如画笔,画刷,区域等.WTL对所有的Win32 (和W2K) 通用对话框进行了封装.同样尽管简单,但是它的确使请求字体或者文件变的非常的简单.合成了旧的AtlControls.h,新加了一些封装类. 这些封装类封装了W2K控件,以及一些不属于Win32的"控件",像Command Bar, bitmap button, hyperlink 和 wait cursor.WTL 最终把消息分离带入了ATL! 一些新的MSG映射宏将消息分离,调用您类里的消息处理函数.消息处理函数的参数的值是从消息分离得到的.唯一令人头痛的是,您需要查看头文件以确定函数参数的意义.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

snaill

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值