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

【玩转STM32】:Default_Handler问题 记录代码进入Default_Handler错误的解决办法 1 问题表述 在一次调试代码的时候,发现代码卡死在启动文件 startup_stm32l431xx_.s 的355行,即 B. 处 B.是汇编代码,B:跳转到一个标号,这里跳转到一个‘.’,即表示无限死循环 通过在Debug窗口可以定位到,程序是进入Default_Handler错误 2 问题分析 使用某个外设的时候,开启了某个中断,但是又忘记编写配套的中断服务程序或者函数名写错,那当中断来临的时,程序就会跳转到启动文件预先写. 阅读详情

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

MFC程序员精通WTL的实用指南与案例 WTL的核心组件主要围绕着窗口和控件的创建、消息处理和界面更新展开。这些组件包括:窗口类模板:WTL提供了一系列预定义的窗口类模板,用于创建窗口、控件、对话框等。这些类模板简化了传统Windows编程中复杂的消息处理和窗口管理。消息映射机制:WTL采用宏定义和模板函数实现了一套灵活的消息映射机制,允许开发者以声明的方式将消息与处理函数关联起来。控件封装:WTL对常见的标准控件进行了封装,提供了简洁的接口来创建和操作控件,例如按钮、编辑框等。图形设备接口(GDI)扩展。 阅读详情

相关推荐

WTL学习笔记——(6)对话框与控件

MFC 的对话框和控件的封装真得可以节省你很多时间和功夫。没有MFC对控件的封装,你要操作控件就得耐着性子填写各种结构并写很多的SendMessage调用。MFC还提供了对话框数据交换(DDX),它可以在控件和变量之间传输数据。WTL 当然也提供了这些功能,并对控件的封装做了很多改进。本文将着眼于一个基于对话框的程序演示你以前用MFC实现的功能,除此之外还有WTL消息处理的增强功能。第五章将介绍高

Koma的主页 2102

MFCWTL 、ATL、STL联系与区别

MFCWTL 、ATL、STL联系与区别    这个要先从C++和VC++说起!    c++是一门语言,它与平台无关。只要能提供c++编译器(或者交叉编译器)的平台,就能用c++编程。基本上常见的操作系统都有C++编译器或交叉编译器,所以你可以认为几乎所有的平台都可以使用C++编程。   首先要明确,严格来说VC++不是语言,而是一个工具软件。他甚至不是编译器,而是一个开发环境(

学海无涯 2535

MFC,ATL,WTL的历史沿袭

需求推动了技术的发展,从MFC到ATL,从ATL再到WTL的发展历程我想就是一个最好的见证。 早期的VC++开发者们发现了MFC(Microsoft Foundation Classes) 这样一个好东东。他们发现,MFC提供了一个强大的类库,很好的满足了面向对象编程的需要。随着泛型编程技术的发展和时间的推移,慢慢地,他们慢慢觉得MFC的类库过于庞大和宽泛,而且它提供的模板库只覆盖了很有限的

numbibi的专栏 1485

MFCWTL、WPF、wxWidgets、Qt、GTK的对比

WTL都算不上什么Framework,就是利用泛型特性对Win API做了层封装,设计思路也没摆脱MFC的影响,实际上用泛型做UI Framework也只能算是一次行为艺术,这个思路下继续发展就会变得没法用了,比如 代码过于复杂,编译太慢,出错不好调试等问题难以解决。 而且封装得也不完全,还是随处可见 HWND HDC之类的东西。 用途主要是写一些很小的程序,或者作为其他UI框架的后端实现

小豪之家 5577

译:MFC 程序员的 WTL 教程(一)

第一部分 - ATL 中的 GUI 类 下载示例工程 - 24K 本章内容README.TXT 本系列介绍 第一部分介绍 ATL 背景知识 ATL 和 WTL 的历史 ATL 风格的模板 ATL 窗口类 定义窗口实现 填充消息映射高级消息映射链和嵌入(Mix-in)类 ATL EXE 的结构 ATL 中的对话框

路径的专栏 8527

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

《程序员修炼之道》说当你想说这不可能的时候,往往是你在调用的方法上出现了错误。我们重新回到起点,来看看那里出了错。仔细地研读代码以后发现,事件是怎么传递到MSG_MAP的呢?难道我们通过赋值将一个窗体句柄传进来,我们在这个类中定义的MSG_MAP就能自动的连接到这个句柄上吗?这显然是真的不可能。那么没有将MSG_MAP连接到窗体句柄很可能是控件类无法收到任何事件的原因。那么如何将MSG_M

蜗牛档案室 2244

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

  WTL for MFC Programming实践篇                    --- 一个自定义ComboBox移植过程                    --- 蜗牛手记        现在有一个MFC写的自定义ComboBox打算移植WTL上,于是根据WTL的书写方法修改了程序,就得到下面的代码: Class CComboBoxEx : public CComboBo

Zaobird的专栏 1304

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

  《程序员修炼之道》说当你想说这不可能的时候,往往是你在调用的方法上出现了错误。 我们重新回到起点,来看看那里出了错。仔细地研读代码以后发现,事件是怎么传递到MSG_MAP的呢?难道我们通过赋值将一个窗体句柄传进来,我们在这个类中定义的MSG_MAP就能自动的连接到这个句柄上吗?这显然是真的不可能。 那么没有将MSG_MAP连接到窗体句柄很可能是控件类无法收到任何事件的原因。那么如何将MSG_M

Zaobird的专栏 1408

[WTL]WTL for MFC Programming实践篇 --- 一个自定义ComboBox移植过程

[WTL]WTL for MFC Programming实践篇 --- 一个自定义ComboBox移植过程 现在有一个MFC写的自定义ComboBox打算移植WTL上,于是根据WTL的书写方法修改了程序,就得到下面的代码: Class CComboBoxEx : public CComboBo...

weixin_30826095的博客 108

SDK编程,MFC编程,WTL编程之间的关系

SDK编程、MFC编程和WTL编程是Windows平台开发中三种不同的技术路径,其关系可概括为。

felicity_one的博客 590

WTL:如何绘制ComboBox

首先给大家介绍一个csdn博客关于ComboBox的组成和如何绘制的介绍。 http://blog.csdn.net/fengbangyue/article/details/5222124 我要绘制的是drop list模式的ComboBox。 直接上代码: //下拉列表框 class ComboBox : public CWindowImpl,public COwnerDraw {

周洁伦之谜_相信我 3228

MFC程序员的WTL指南之WTL 界面基类

现在正式开始介绍WTL!在这一部分我讲的内容包括生成一个基本的主窗口和WTL提供的一些友好的改进,比如UI界面的更新(如菜单上的选择标记)和更好的消息映射机制。为了更好地掌握本章的内容,你应该安装WTL并将WTL库的头文件目录添加到VC的搜索目录中,还要将WTL的应用程序生成向导复制到正确的位置。WTL的发布版本中有文档具体介绍如何做这些设置,如果遇到困难可以查看这些文档。   WTL 总体印象

★果冻★的专栏 3995

[WTL/ATL]_[中级]_[自定义按钮2]

场景 在自定义按钮1里我们通过处理WM_PAINT消息来达到绘制按钮的目的, 并通过BCN_HOTITEMCHANGE通知来处理鼠标进入和离开状态. 按钮控件有没有其他方式来绘制呢?不需要通过WM_PAINT事件, 并且在绘制前就能知道按钮当前的状态? 说明 当创建按钮的样式增加BS_OWNERDRAW时, 按钮会接收到WM_DRAWITEM事件. 这时候我们可以通过处理WM_DRAWITE...

小白 877

ComboBox控件-

转至http://zaobird.blogdriver.com/zaobird/ WTL for MFC Programming实践篇 --- 一个自定义ComboBox移植过程 --- 蜗牛手记 现在有一个MFC写的自定义ComboBox打算移植WTL上,于是根据WTL的书写方法修改了...

weixin_30598225的博客 142

[转]剖析ATL、WTL CString的实现

源于http://hi.baidu.com/xiaoyaoyiyiyun/blog/item/c6d102fa3989e9344f4aea86.html 话说CString这个东西困扰了很多年轻人,因为它会引起诡异的编译错误,今天跟着我一起来深入ATL、WTL头文件,来把这个东西搞个清清楚楚。 【涉及到头文件】   ATL: atlstr.h, atlsimpstr.h   MFC : ...

weixin_30825199的博客 332
上一篇: WTL for MFC Programmers(2)
下一篇: [原创] WTL for MFC Programming实践篇 --- 一个自定义ComboBox的移植过程(下)
snaill
博客等级 码龄27年 599粉丝 159原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值