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

WTL 专栏收录该内容
7 篇文章 0 订阅

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
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

目录 WTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Chinese Version Prologue ................................ ........................... 6 WTL for MFC Programmers, Part I - ATL GUI Classes ................................ ........................... 8 README.TXTREADME.TXT README.TXTREADME.TXT README.TXTREADME.TXT ................................ ................................ ................................ ......................... 8 对本系列文章总体介绍 对本系列文章总体介绍 对本系列文章总体介绍 ................................ ................................ ................................ ....... 9 对第一章简单介绍 对第一章简单介绍 ................................ ................................ ................................ ............. 11 ATL ATL ATL 背景知识 ................................ ................................ ................................ ....................... 11 ATL 和 WTL 发展历史 ................................ ................................ ......................... 12 ATL-style 模板 ................................ ................................ ................................ ............. 12 ATL ATL ATL 窗口类 ................................ ................................ ................................ ........................... 15 定义一个窗口实现 定义一个窗口实现 ................................ ................................ ................................ ............. 17 填写消息映射链 ................................ ................................ ................................ ............. 19 高级消息映射链和嵌 高级消息映射链和嵌 入类 ................................ ................................ ................................ ..... 21 ATLATL 程序结构 ................................ ................................ ................................ .................... 24 ATLATL 中对话框 ................................ ................................ ................................ .................... 27 WTL for MFC Programmers, Part II - WTL GUI Base Classes ................................ ............. 32 对第二部分介绍 对第二部分介绍 ................................ ................................ ................................ ................. 32 WTL WTL WTL WTL 总体印象 ................................ ................................ ................................ ...................... 32 开始写 WTLWTLWTL程序 ................................ ................................ ................................ .................. 33 WTL WTL WTL WTL 对消息映射增强 ................................ ................................ ................................ ...... 36 从 WTLWTLWTL应用程序生成向导能得到什么 应用程序生成向导能得到什么 应用程序生成向导能得到什么 ................................ ................................ .......... 42 使用向导整个过程 ................................ ................................ ................................ ..... 42 查看生成代码 ................................ ................................ ................................ ............. 44 CMessageLoop CMessageLoop CMessageLoop CMessageLoop CMessageLoop CMessageLoop CMessageLoop CMessageLoop 内部实现 ................................ ................................ ................................ .. 47 CFrameWindowImpl CFrameWindowImpl CFrameWindowImpl CFrameWindowImpl CFrameWindowImpl CFrameWindowImpl CFrameWindowImpl CFrameWindowImpl CFrameWindowImpl CFrameWindowImpl CFrameWindowImpl CFrameWindowImpl CFrameWindowImpl 内部实现 ................................ ................................ ......................... 48 回到前面时钟程序 回到前面时钟程序 ................................ ................................ ................................ ............. 50 界面元素自动更新 界面元素自动更新 (UI Updating)(UI Updating)(UI Updating)(UI Updating) (UI Updating) (UI Updating)(UI Updating)(UI Updating)(UI Updating) (UI Updating) ................................ ................................ ...................... 51 添加控制时钟新菜单项 ................................ ................................ ............................. 52 调用 UIEnable()................................ ................................ ................................ ............ 53 消息映射链中最后需要注意地方 消息映射链中最后需要注意地方 消息映射链中最后需要注意地方 消息映射链中最后需要注意地方 ................................ ................................
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值