[源代码下载:http://download.csdn.net/source/3522801]
MFC中,对话框和控件的封装节省了我们大量的时间和成本,否则我们需要编写大量的消息处理来管理各个控件。MFC提供了Dialog Data Exchange(DDX,对话框数据交换,对话框和变量之间的数据传输)的功能。WTL同样支持这些特性,并且在一写公共控件的封装类中做了一些改进。本文用一个给予对话框的程序来展示过去使用的MFC的一些特性,以及WTL的一些消息处理的增强功能,对于高级UI特性和WTL中新增的控件,下章将会讲到。
ATL有两个对话框类,CDialogImpl
和 CAxDialogImpl,后者用于ActiveX控件,本章暂不涉及。
创建一个对话框必须要做的三件事情:
1)创建对话框资源
2)创建一个派生于CDialogImpl的新类
3)创建一个共有成员的对话框ID
然后,就可像在前几章的框架窗口中一样添加消息处理。
WTL有用大量的控件封装类,它们的命名和方法与MFC中很像。我们可以使用MFC的相关文档来帮助我们使用WTL。按F12键也可以很方便的转到类和方法的定义处。
内置控件的封装类有:
1)User Controls:CStatic
, CButton
, CListBox
,CComboBox
,CEdit
,CScrollBar
,CDragListBox
2)Common controls:CImageList
, CListViewCtrl
(CListCtrl
in MFC),CTreeViewCtrl
(CTreeCtrl
in MFC),CHeaderCtrl
,CToolBarCtrl
,CStatusBarCtrl
,CTabCtrl
,CToolTipCtrl
,CTrackBarCtrl
(CSliderCtrl
in MFC),CUpDownCtrl
(CSpinButtonCtrl
in MFC),CProgressBarCtrl
,CHotKeyCtrl
,CAnimateCtrl
,CRichEditCtrl
,CReBarCtrl
,CComboBoxEx
,CDateTimePickerCtrl
,CMonthCalendarCtrl
,CIPAddressCtrl
3)不在MFC中的公共控件:CPagerCtrl
, CFlatScrollBar
, CLinkCtrl
(可点击的超链接,在XP系统及其之后有效)
4)WTL特有的封装类:CBitmapButton
, CCheckListViewCtrl
(list view control with check boxes),CTreeViewCtrlEx
andCTreeItem
(used together,CTreeItem
wraps anHTREEITEM
),CHyperLink
(可点击的超链接,所有操作系统有效)
注意:大部分的封装类是一个窗口类,像CWindow。它们封装一个窗口句柄以及一些消息处理,如CListBox::GetCurSel()
封装了消息LB_GETCURSEL。因此像CWindow一样,我们可以创建为一个已存在控件创建一个临时的控件封装类。同样,当控件封装类析构时,控件本身并不销毁。其中,
CBitmapButton
,CCheckListViewCtrl
, 和CHyperLink除外。
本篇文章是针对有MFC开发经验的人员,因此,不花费大量的时间去讲解这些封装类的细节。
创建一个模态对话框,过程略。
程序的入口函数代码:
这段代码首先使用单线程套间初始化COM,对于使用ActiveX控件,这是必须的。如果程序不使用COM,可以移除CoInitialize()和CoUninitialize()的调用代码。然后,调用WTL基础函数
AtlInitCommonControls(),它封装了函数
InitCommonControlsEx()。接着,初始化全局
和COM,并把对话框的返回值作为程序退出码。_Module,显示主对话框界面,通过DoModal()创建的ATL对话框实际上是模态的;而MFC中所有的对话框都是非模态的,MFC通过手工禁用对话框的父窗口来模拟模态的形式。最后,卸载
_Module
CMainDlg
变量外的BLOCK是很重要的,因为CMainDlg类中可能会有使用了ATL和WTL的成员变量。这些成员变量的析构函数中也可能会使用ATL和WTL特性。如果这对儿大括号不存在,CMainDlg
的析构函数(以及成员变量的析构函数)将会在_Module.Term()(反初始化ATL和WTL)
之后被调用并且尝试使用ATL和WTL特性。这将会导致一个很难诊断原因的崩溃。
既然使用了List View 控件,AtlInitCommonControls()
的调用参数需要改变。比如,可以改为:
这会注册一些不需要的控件,但是,当我们再往对话框中添加其他一些不同类型的控件时,我们就不需要再添加相应的ICC_*常量了。
有几种方法可以使控件和成员变量关联起来。一些空间使用CWindow或其他窗体接口类(如CListViewCtrl),而其他是CWindowImpl的派生类。如果仅仅需要一个临时变量,使用CWindow对象就可以了,但是如果需要子类化控件并且向其传递消息,就需要使用
CWindowImpl。
这是最简单的方法,声明一个CWindow或其他窗体类,通过Attach方法或CWindow的构造函数或分配运算关联变量和控件窗口句柄。
注意:CWindow的析构函数并不销毁窗体,因此在他们超出作用域前,不需要Detach该变量。因此,可以在OnInitDialog()中绑定控件和变量。
CContainedWindow类是使用CWindow和CWindowImpl类的中间类,它允许子类化控件,并在控件的父窗口处理该控件的消息。这将会导致我们要在对话框类中处理所有消息,我们也不需要为每个控件实现独立的CWindowImpl类。注意:无法使用CContainedWindow处理WM_COMMAND
,WM_NOTIFY以及其他通知类消息。因为这些消息总是发送到控件的父窗口。
CContainedWindowT是一个把窗体接口类作为模板参数的模板类。该模板的一个特例化
CContainedWindowT<CWindow>就是CContainedWindow。
要使用CContainedWindow,需要做四件事情:
1)在对话框类中创建
CContainedWindowT类型的成员
类中添加
2)在对话框消息路由中把消息处理添加ALT_MSG_MAP段
3)在对话框的构造函数中,调用CContainedWindowT的构造函数并且告知它将路由哪个
ALT_MSG_MAP段
4)OnInitDialog()函数中,调用
CContainedWindowT::SubclassWindow()关联控件和变量
例子中,我们对OK和Cancel按钮使用CContainedWindow,并且处理
WM_SETCURSOR消息,用于改变鼠标形式。
首先在CMainDlg
CContainedWindow
成员变量
第二,添加ALT_MSG_MAP消息路由段,OK按钮使用1号段,Cancel使用2号段,这意味着所有发送到OK按钮的消息将在
中路由,所有发送到Cancel按钮的消息将在ALT_MSG_MAP(1)
ALT_MSG_MAP(2)中路由。
第三步:在CMainDlg的构造函数中,调用 CContainedWindow 的构造函数并告知使用哪个ALT_MSG_MAP
段。
CContainedWindow
的构造函数参数有两个,第一个是CMessageMap*类型,第二个是
ALT_MSG_MAP
段的索引。前者一般使用this,意思是要使用对话框自身的消息路由,后者告知该对象的消息要在哪个ALT_MSG_MAP段中路由。
派生的类,做了以下几件事,运行时将会出现assert失败。
注意:如果使用VC 7.0 or 7.1 and WTL 7.0 or 7.1,以及以后的版本,如果一个由CWindowImpl或
CDialogImpl
1)消息路由使用BEGIN_MSG_MAP,而不是
BEGIN_MSG_MAP_EX
2)消息路由中包含一个ALT_MSG_MAP
段
3)CContainedWindowT类型的变量路由消息到此
ALT_MSG_MAP
段中
4)这个ALT_MSG_MAP段中使用WTL消息路由宏
解决办法是:使用BEGIN_MSG_MAP_EX。
最后,为每个CContainedWindow成员关联一个控件:
下面是WM_SETCURSOR的消息处理:
注:如果想使用CButton的特性和方法,可以这样定义成员变量:
第三种方法需要创建CWindowImpl的派生类并用之子类化控件。这很像第二种方法,但是消息的处理放在了CWindowImpl的派生类中。
例子中的About 按钮就是使用子类化的方法:首先为该控件创建一个CButtonImpl类,它从CWindowImpl派生并处理消息
WM_SETCURSOR。
然后,在对话框类中创建CButtonImpl类型的成员变量:
最后在OnInitDialog()中子类化这个控件:
WTL的对话框数据变化机制很像MFC中的DDX。这同样需要为控件创建CWindowImpl的派生类。为了使用DDX,需要在头文件中添加
首先,为使用DDX,需要在对话框的继承列表中添加CWinDataExchange
然后,创建一个控件的派生类并定义一个成员变量,然后像MFC一样,创建一个DDX路由表
最后,在OnInitDialog()中,调用从
CWinDataExchange继承来的方法
DoDataExchange()。第一次调用
DoDataExchange()时,将会对控件进行子类化,并关联控件和成员变量。
DoDataExchange()
的参数的含义与MFC中UpdateData()一样。当鼠标右键点击这个文本编辑框时,将会弹出消息框。
DDX_CONTROL_HANDLE
宏是WTL7.1以后新增的宏。在之前版本,如果想使用DDX关联平面窗体接口类(如:CWindow
,CListViewCtrl
),将无法使用DDX_CONTROL
,因为它仅仅支持CWindowImpl
的派生类,而DDX_CONTROL_HANDLE
可以。
如果使用WTL7.0,可以定义一个宏来定义一个CWindowImpl
的派生类:
然后使用
就可以创建一个从CWindowImpl
派生的CListViewCtrl_ddx类,它可以用于
DDX_CONTROL。
WTL支持文本框和字符串变量之间的数据交换,也可以解析字符串为一个数值,并可以转换为整数或浮点数。WTL也支持复选框和多选框的状态和整型变量间的交换。
DDX_TEXT
CString
,
BSTR
,
CComBSTR
, or statically-allocated character array. Using an array allocated with
new
will not work.
DDX_INT
int
.
DDX_UINT
unsignedint
.
DDX_FLOAT
float
or
double
.
DDX_CHECK
int
or
bool
.
DDX_RADIO
int
.
DDX_CHECK
can take either an int
orbool
variable. Theint
version accepts/returns the values 0, 1, and 2 (or equivalently,BST_UNCHECKED
,BST_CHECKED
, andBST_INDETERMINATE
). Thebool
version (added in WTL 7.1) can be used when a check box will never be in the indeterminate state; this version accepts/returnstrue
if the check box is checked, orfalse
if it's unchecked. If the check box happens to be in the indeterminate state,DDX_CHECK
returnsfalse
.
There is also an additional floating-point macro that was added in WTL 7.1:
DDX_FLOAT_P(controlID, variable, precision)
DDX_FLOAT
, but when setting the text in an edit box, the number is formatted to show at most
precision
significant digits.
注意:当使用DDX_FLOAT
和 DDX_FLOAT_P 时,需要添加宏定义
默认情况下,为了优化,是禁止浮点数支持的。
可以像使用MFC中的UpdateData()一样使用DoDataExchange(),它的原型:
bSaveAndValidate
nCtlID
DoDataExchange()如果执行成功,返回TRUE,否则返回FALSE。在对话框类中,有两个函数可以重载用于错误处理。1)
OnDataExchangeError(),当不管以任何原因导致数据交换失败时,都会被调用。
CWinDataExchange中的默认处理办法是:发出一个蜂鸣音,并将导致出错的控件获得焦点。
2)OnDataValidateError(),用于数据有效性检测。
首先用DDX在CMainDlg中添加一对变量
在“确定”按钮的处理中,首先调用DoDataExchange(),将文本框中的字符串转换成刚刚添加的两个变量。将结果显示在列表框中。
此时,如果在文本框中输入非数值文本,DDX_INT
将会失败,并调用OnDataExchangeError(),重载本函数
WM_DRAWITEM(当一个所有者绘制的控件需要被绘制使将会被触发)。父窗口可以处理这些消息本身,或者将这些消息反射回控件---像MFC,控件可以自己处理通知,使代码相对独立,易迁移。
作为WM_NOTIFY
和 WM_COMMAND发送的通知包含很多信息。
WM_COMMAND
消息的参数中包含发送消息的控件ID,控件句柄以及通知编码。WM_NOTIFY
消息不仅包括上述所有信息,还有一个指向NMHDR结构的指针。ATL和WTL有很多相关的宏来处理这些通知消息的路由。本文中仅覆盖WTL的宏,注意:消息路由开始要使用
BEGIN_MSG_MAP_EX,并且在预编译头文件中添加#include <atlcrack.h>
处理 WM_COMMAND
通知消息,可使用下面几个宏的一个:
COMMAND_HANDLER_EX(id, code, func)
COMMAND_CODE_HANDLER_EX(id, func)
COMMAND_ID_HANDLER_EX(code, func)
COMMAND_RANGE_HANDLER_EX(idFirst, idLast, func)
COMMAND_RANGE_CODE_HANDLER_EX(idFirst, idLast, code, func)
例如:
1)COMMAND_HANDLER_EX(IDC_USERNAME, EN_CHANGE, OnUsernameChange):处理IDC_USERNAME的文本框发送的EN_CHANGE消息,处理函数是OnUsernameChange.
2)COMMAND_ID_HANDLER_EX(IDOK, OnOK):处理IDOK控件触发的所有通知。
3)COMMAND_RANGE_CODE_HANDLER_EX(IDC_MONDAY, IDC_FRIDAY, BN_CLICKED, OnDayClicked)
:处理[IDC_MONDAY, IDC_FRIDAY]范围内所有控件发出的BN_CLICKED通知消息。
处理WM_NOTIFY通知消息的宏,与上述类似,只需把
COMMAND_替换为NOTIFY_
WM_COMMAND消息处理函数的原型:
WM_COMMAND消息不使用返回值,因此其消息处理函数可以返回void。
WM_NOTIFY的消息处理函数的原型:
消息处理函数的返回值用于消息的结果,这点不同于MFC,该处理函数接收LRESULT*数据并用于消息结果。通知标识和发送通知的控件句柄是NMHDR结构的成员。使用时需要将phdr转换为正确的类型。
实例中,我们在CMainDlg中添加列表框的LVN_ITEMCHANGED的通知消息的处理,用于在对话框中显示当前选择的元素:
本例中并没有使用phdr,但是作为示范,我们将之转换为NMLISTVIEW*
类型数据。
如果一个从CWindowImpl派生的类实现一个控件,如之前的CEditImpl,那么我们就可以在该类中处理通知,而不是在父对话框中。这就叫做通知反射,它有些类似于MFC中的消息反射。不同点在于WTL中控件和父控件均参与反射,而MFC仅仅控件本身参与反射。
当你想将通知消息反射回控件类时,只需使用宏REFLECT_NOTIFICATIONS()
REFLECT_NOTIFICATIONS()在消息路由中添加一些代码,用于处理那些在之前的宏中没有被处理的任何通知消息。代码检查消息的
HWND并将消息转发的对应控件窗口类中。在OLE控件中,进行消息反射后,消息的值将会被改变,如使用OCM_xxx替换
WM_xxx,否则消息的处理与未反射的一样。
可被反射的消息有18种:
WM_COMMAND
, WM_NOTIFY
, WM_PARENTNOTIFY
所有者绘制:WM_DRAWITEM
, WM_MEASUREITEM
, WM_COMPAREITEM
, WM_DELETEITEM
WM_VKEYTOITEM
, WM_CHARTOITEM
WM_HSCROLL
, WM_VSCROLL
, WM_CTLCOLOR*
在控件类中,处理感兴趣的反射消息,然后在最后添加DEFAULT_REFLECTION_HANDLER()宏,
DEFAULT_REFLECTION_HANDLER()确保为处理的消息在
DefWindowProc()被正确的路由。下面是所有者绘制按钮的反射消息
WM_DRAWITEM的处理
WTL拥有MSG_OCM_*
的宏,用于其他17中可被反射的消息。既然WM_NOTIFY
和 WM_COMMAND的消息参数在使用时需要解码,WTL为之提供了特殊的宏
MSG_OCM_COMMAND
和MSG_OCM_NOTIFY。这些宏的使用类似
COMMAND_HANDLER_EX
和NOTIFY_HANDLER_EX,但是需要添加前缀
REFLECTED_:
处控件处理TVN_ITEMEXPANDING
消息。CMainDlg中的成员m_wndTree用DDX关联该树控件,并且在
CMainDlg中反射消息:
原文: WTL for MFC Programmers, Part IV - Dialogs and Controls
转自:http://blog.csdn.net/wcyoot/article/details/6674808