[源代码下载:http://download.csdn.net/source/3522801]
MFC中,对话框和控件的封装节省了我们大量的时间和成本,否则我们需要编写大量的消息处理来管理各个控件。MFC提供了Dialog Data Exchange(DDX,对话框数据交换,对话框和变量之间的数据传输)的功能。WTL同样支持这些特性,并且在一写公共控件的封装类中做了一些改进。本文用一个给予对话框的程序来展示过去使用的MFC的一些特性,以及WTL的一些消息处理的增强功能,对于高级UI特性和WTL中新增的控件,下章将会讲到。
Refresher on ATL Dialogs
ATL有两个对话框类,CDialogImpl
和 CAxDialogImpl,后者用于ActiveX控件,本章暂不涉及。
创建一个对话框必须要做的三件事情:
1)创建对话框资源
2)创建一个派生于CDialogImpl的新类
3)创建一个共有成员的对话框ID
然后,就可像在前几章的框架窗口中一样添加消息处理。
Control Wrapper Classes
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开发经验的人员,因此,不花费大量的时间去讲解这些封装类的细节。
Creating a Dialog-Based App with the AppWizard
创建一个模态对话框,过程略。
程序的入口函数代码:
int WINAPI _tWinMain (
HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/,
LPTSTR lpstrCmdLine, int nCmdShow )
{
HRESULT hRes = ::CoInitialize(NULL);
AtlInitCommonControls(ICC_COOL_CLASSES | ICC_BAR_CLASSES);
hRes = _Module.Init(NULL, hInstance);
int nRet = 0;
// BLOCK: Run application
{
CMainDlg dlgMain;
nRet = dlgMain.DoModal();
}
_Module.Term();
::CoUninitialize();
return nRet;
}
这段代码首先使用单线程套间初始化COM,对于使用ActiveX控件,这是必须的。如果程序不使用COM,可以移除CoInitialize()和CoUninitialize()的调用代码。然后,调用WTL基础函数
和COM,并把对话框的返回值作为程序退出码。AtlInitCommonControls(),它封装了函数
InitCommonControlsEx()。接着,初始化全局
_Module,显示主对话框界面,通过DoModal()创建的ATL对话框实际上是模态的;而MFC中所有的对话框都是非模态的,MFC通过手工禁用对话框的父窗口来模拟模态的形式。最后,卸载
_Module
CMainDlg
变量外的BLOCK是很重要的,因为CMainDlg类中可能会有使用了ATL和WTL的成员变量。这些成员变量的析构函数中也可能会使用ATL和WTL特性。如果这对儿大括号不存在,CMainDlg
的析构函数(以及成员变量的析构函数)将会在_Module.Term()(反初始化ATL和WTL)
之后被调用并且尝试使用ATL和WTL特性。这将会导致一个很难诊断原因的崩溃。
既然使用了List View 控件,AtlInitCommonControls()
的调用参数需要改变。比如,可以改为:
AtlInitCommonControls ( ICC_WIN95_CLASSES );
这会注册一些不需要的控件,但是,当我们再往对话框中添加其他一些不同类型的控件时,我们就不需要再添加相应的ICC_*常量了。
Using the Control Wrapper Classes
有几种方法可以使控件和成员变量关联起来。一些空间使用CWindow或其他窗体接口类(如CListViewCtrl),而其他是CWindowImpl的派生类。如果仅仅需要一个临时变量,使用CWindow对象就可以了,但是如果需要子类化控件并且向其传递消息,就需要使用
CWindowImpl。
ATL Way 1 - Attaching a CWindow
这是最简单的方法,声明一个CWindow或其他窗体类,通过Attach方法或CWindow的构造函数或分配运算关联变量和控件窗口句柄。
HWND hwndList = GetDlgItem(IDC_LIST);
CListViewCtrl wndList1 (hwndList); // use constructor
CListViewCtrl wndList2, wndList3;
wndList2.Attach ( hwndList ); // use Attach method
wndList3 = hwndList; // use assignment operator
注意:CWindow的析构函数并不销毁窗体,因此在他们超出作用域前,不需要Detach该变量。因此,可以在OnInitDialog()中绑定控件和变量。
ATL Way 2 - CContainedWindow
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
成员变量
class CMainDlg : public CDialogImpl<CMainDlg>
{
// ...
protected:
CContainedWindow m_wndOKBtn, m_wndExitBtn;
};
第二,添加ALT_MSG_MAP消息路由段,OK按钮使用1号段,Cancel使用2号段,这意味着所有发送到OK按钮的消息将在
中路由,所有发送到Cancel按钮的消息将在ALT_MSG_MAP(1)
ALT_MSG_MAP(2)中路由。
class CMainDlg : public CDialogImpl<CMainDlg>
{
public:
BEGIN_MSG_MAP_EX(CMainDlg)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
COMMAND_ID_HANDLER(IDOK, OnOK)
COMMAND_ID_HANDLER(IDCANCEL, OnCancel)
ALT_MSG_MAP(1)
MSG_WM_SETCURSOR(OnSetCursor_OK)
ALT_MSG_MAP(2)
MSG_WM_SETCURSOR(OnSetCursor_Exit)
END_MSG_MAP()
LRESULT OnSetCursor_OK(HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg);
LRESULT OnSetCursor_Exit(HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg);
};
第三步:在CMainDlg的构造函数中,调用 CContainedWindow 的构造函数并告知使用哪个ALT_MSG_MAP
段。
CMainDlg::CMainDlg() : m_wndOKBtn(this, 1),
m_wndExitBtn(this, 2)
{
}
CContainedWindow
的构造函数参数有两个,第一个是CMessageMap*类型,第二个是
派生的类,做了以下几件事,运行时将会出现assert失败。ALT_MSG_MAP
段的索引。前者一般使用this,意思是要使用对话框自身的消息路由,后者告知该对象的消息要在哪个ALT_MSG_MAP段中路由。
注意:如果使用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成员关联一个控件:
LRESULT CMainDlg::OnInitDialog(...)
{
// ...
// Attach CContainedWindows to OK and Exit buttons
m_wndOKBtn.SubclassWindow ( GetDlgItem(IDOK) );
m_wndExitBtn.SubclassWindow ( GetDlgItem(IDCANCEL) );
return TRUE;
}
下面是WM_SETCURSOR的消息处理:
LRESULT CMainDlg::OnSetCursor_OK (
HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg )
{
static HCURSOR hcur = LoadCursor ( NULL, IDC_HAND );
if ( NULL != hcur )
{
SetCursor ( hcur );
return TRUE;
}
else
{
SetMsgHandled(false);
return FALSE;
}
}
LRESULT CMainDlg::OnSetCursor_Exit (
HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg )
{
static HCURSOR hcur = LoadCursor ( NULL, IDC_NO );
if ( NULL != hcur )
{
SetCursor ( hcur );
return TRUE;
}
else
{
SetMsgHandled(false);
return FALSE;
}
}
注:如果想使用CButton的特性和方法,可以这样定义成员变量:
CContainedWindowT<CButton> m_wndOKBtn;
ATL Way 3 - Subclassing
第三种方法需要创建CWindowImpl的派生类并用之子类化控件。这很像第二种方法,但是消息的处理放在了CWindowImpl的派生类中。
例子中的About 按钮就是使用子类化的方法:首先为该控件创建一个CButtonImpl类,它从CWindowImpl派生并处理消息
WM_SETCURSOR。
class CButtonImpl : public CWindowImpl<CButtonImpl, CButton>
{
BEGIN_MSG_MAP_EX(CButtonImpl)
MSG_WM_SETCURSOR(OnSetCursor)
END_MSG_MAP()
LRESULT OnSetCursor(HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg)
{
static HCURSOR hcur = LoadCursor ( NULL, IDC_SIZEALL );
if ( NULL != hcur )
{
SetCursor ( hcur );
return TRUE;
}
else
{
SetMsgHandled(false);
return FALSE;
}
}
};
然后,在对话框类中创建CButtonImpl类型的成员变量:
class CMainDlg : public CDialogImpl<CMainDlg>
{
// ...
protected:
CButtonImpl m_wndAboutBtn;
};
最后在OnInitDialog()中子类化这个控件:
LRESULT CMainDlg::OnInitDialog(...)
{
// ...
// CButtonImpl: subclass the About button
m_wndAboutBtn.SubclassWindow ( GetDlgItem(ID_APP_ABOUT) );
return TRUE;
}
WTL Way 1 - DDX_CONTROL
WTL的对话框数据变化机制很像MFC中的DDX。这同样需要为控件创建CWindowImpl的派生类。为了使用DDX,需要在头文件中添加
#include <atlddx.h>
首先,为使用DDX,需要在对话框的继承列表中添加CWinDataExchange
class CMainDlg : public CDialogImpl<CMainDlg>,
public CWinDataExchange<CMainDlg>
{
//...
};
然后,创建一个控件的派生类并定义一个成员变量,然后像MFC一样,创建一个DDX路由表
class CEditImpl : public CWindowImpl<CEditImpl, CEdit>
{
BEGIN_MSG_MAP_EX(CEditImpl)
MSG_WM_CONTEXTMENU(OnContextMenu)
END_MSG_MAP()
void OnContextMenu ( HWND hwndCtrl, CPoint ptClick )
{
MessageBox("Edit control handled WM_CONTEXTMENU");
}
};
class CMainDlg : public CDialogImpl<CMainDlg>,
public CWinDataExchange<CMainDlg>
{
//...
BEGIN_DDX_MAP(CMainDlg)
DDX_CONTROL(IDC_EDIT, m_wndEdit)
END_DDX_MAP()
protected:
CContainedWindow m_wndOKBtn, m_wndExitBtn;
CButtonImpl m_wndAboutBtn;
CEditImpl m_wndEdit;
};
最后,在OnInitDialog()中,调用从
CWinDataExchange继承来的方法
DoDataExchange()。第一次调用
DoDataExchange()时,将会对控件进行子类化,并关联控件和成员变量。
LRESULT CMainDlg::OnInitDialog(...)
{
// ...
// Attach CContainedWindows to OK and Exit buttons
m_wndOKBtn.SubclassWindow ( GetDlgItem(IDOK) );
m_wndExitBtn.SubclassWindow ( GetDlgItem(IDCANCEL) );
// CButtonImpl: subclass the About button
m_wndAboutBtn.SubclassWindow ( GetDlgItem(ID_APP_ABOUT) );
// First DDX call, hooks up variables to controls.
DoDataExchange(false);
return TRUE;
}
DoDataExchange()
的参数的含义与MFC中UpdateData()一样。当鼠标右键点击这个文本编辑框时,将会弹出消息框。
WTL Way 2 - DDX_CONTROL_HANDLE
DDX_CONTROL_HANDLE
宏是WTL7.1以后新增的宏。在之前版本,如果想使用DDX关联平面窗体接口类(如:CWindow
,CListViewCtrl
),将无法使用DDX_CONTROL
,因为它仅仅支持CWindowImpl
的派生类,而DDX_CONTROL_HANDLE
可以。
如果使用WTL7.0,可以定义一个宏来定义一个CWindowImpl
的派生类:
#define DDX_CONTROL_IMPL(x) \
class x##_ddx : public CWindowImpl<x##_ddx, x> \
{ public: DECLARE_EMPTY_MSG_MAP() };
然后使用
DDX_CONTROL_IMPL(CListViewCtrl)
就可以创建一个从CWindowImpl
派生的CListViewCtrl_ddx类,它可以用于
DDX_CONTROL。
More on DDX
WTL支持文本框和字符串变量之间的数据交换,也可以解析字符串为一个数值,并可以转换为整数或浮点数。WTL也支持复选框和多选框的状态和整型变量间的交换。
DDX macros
-
Transfers text data to/from an edit box. The variable can be a
CString
,BSTR
,CComBSTR
, or statically-allocated character array. Using an array allocated withnew
will not work. -
Transfers numerical data between an edit box and an
int
. -
Transfers numerical data between an edit box and an
unsignedint
. -
Transfers numerical data between an edit box and a
float
ordouble
. -
Transfers the state of a check box to/from an
int
orbool
. -
Transfers the state of a group of radio buttons to/from an
int
.
DDX_TEXT
DDX_INT
DDX_UINT
DDX_FLOAT
DDX_CHECK
DDX_RADIO
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
, and BST_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:
-
Similar to
DDX_FLOAT
, but when setting the text in an edit box, the number is formatted to show at mostprecision
significant digits.
DDX_FLOAT_P(controlID, variable, precision)
注意:当使用DDX_FLOAT
和 DDX_FLOAT_P 时,需要添加宏定义
#define _ATL_USE_DDX_FLOAT
默认情况下,为了优化,是禁止浮点数支持的。
More about DoDataExchange()
可以像使用MFC中的UpdateData()一样使用DoDataExchange(),它的原型:
BOOL DoDataExchange ( BOOL bSaveAndValidate = FALSE,
UINT nCtlID = (UINT)-1 );
- 标志位,指示数据传输的方向. 如果是TRUE,表示数据从控件传递交换到变量中;否则表示数据从变量交换到控件中。注意:此参数默认值为FALSE,而MFC中的UpdateData()的参数的默认值是TRUE。我们同样可是使用宏DDX_SAVE和DDX_LOAD作为参数,这样方便记忆。
- 如果为-1,表示更新所有控件的数据;否则如果仅仅想更新某个控件的数据,将该控件的ID作为此参数。
bSaveAndValidate
nCtlID
DoDataExchange()如果执行成功,返回TRUE,否则返回FALSE。在对话框类中,有两个函数可以重载用于错误处理。1)
OnDataExchangeError(),当不管以任何原因导致数据交换失败时,都会被调用。
CWinDataExchange中的默认处理办法是:发出一个蜂鸣音,并将导致出错的控件获得焦点。
2)OnDataValidateError(),用于数据有效性检测。
Using DDX
首先用DDX在CMainDlg中添加一对变量
class CMainDlg : public ...
{
//...
BEGIN_DDX_MAP(CMainDlg)
DDX_CONTROL(IDC_EDIT, m_wndEdit)
DDX_TEXT(IDC_EDIT, m_sEditContents)
DDX_INT(IDC_EDIT, m_nEditNumber)
END_DDX_MAP()
protected:
// DDX variables
CString m_sEditContents;
int m_nEditNumber;
};
在“确定”按钮的处理中,首先调用DoDataExchange(),将文本框中的字符串转换成刚刚添加的两个变量。将结果显示在列表框中。
LRESULT CMainDlg::OnOK ( UINT uCode, int nID, HWND hWndCtl )
{
CString str;
// Transfer data from the controls to member variables.
if ( !DoDataExchange(true) )
return;
m_wndList.DeleteAllItems();
m_wndList.InsertItem ( 0, _T("DDX_TEXT") );
m_wndList.SetItemText ( 0, 1, m_sEditContents );
str.Format ( _T("%d"), m_nEditNumber );
m_wndList.InsertItem ( 1, _T("DDX_INT") );
m_wndList.SetItemText ( 1, 1, str );
}
此时,如果在文本框中输入非数值文本,DDX_INT
将会失败,并调用OnDataExchangeError(),重载本函数
void CMainDlg::OnDataExchangeError ( UINT nCtrlID, BOOL bSave )
{
CString str;
str.Format ( _T("DDX error during exchange with control: %u"), nCtrlID );
MessageBox ( str, _T("ControlMania1"), MB_ICONWARNING );
::SetFocus ( GetDlgItem(nCtrlID) );
}
下面,添加一个复选框,来演示DDX_CHECK
的用法:
class CMainDlg : public ...
{
//...
BEGIN_DDX_MAP(CMainDlg)
DDX_CONTROL(IDC_EDIT, m_wndEdit)
DDX_TEXT(IDC_EDIT, m_sEditContents)
DDX_INT(IDC_EDIT, m_nEditNumber)
DDX_CHECK(IDC_SHOW_MSG, m_bShowMsg)
END_DDX_MAP()
protected:
// DDX variables
CString m_sEditContents;
int m_nEditNumber;
bool m_bShowMsg;
};
在OnOK的最后,添加m_bShowMsg的测试代码:
void CMainDlg::OnOK ( UINT uCode, int nID, HWND hWndCtl )
{
// Transfer data from the controls to member variables.
if ( !DoDataExchange(true) )
return;
//...
if ( m_bShowMsg )
MessageBox ( _T("DDX complete!"), _T("ControlMania1"),
MB_ICONINFORMATION );
}
Handling Notifications from Controls
WTL中通知类型的消息处理类似于API级别的开发。WTL中,控件以WM_COMMAND或WM_NOTIFY消息的形式给它们的父控件发送通知,父控件负责处理它们。其他的一些消息也别认为是通知,例如WM_DRAWITEM(当一个所有者绘制的控件需要被绘制使将会被触发)。父窗口可以处理这些消息本身,或者将这些消息反射回控件---像MFC,控件可以自己处理通知,使代码相对独立,易迁移。
Handling notifications in the parent
作为WM_NOTIFY
和 WM_COMMAND发送的通知包含很多信息。
WM_COMMAND
消息的参数中包含发送消息的控件ID,控件句柄以及通知编码。WM_NOTIFY
消息不仅包括上述所有信息,还有一个指向NMHDR结构的指针。ATL和WTL有很多相关的宏来处理这些通知消息的路由。本文中仅覆盖WTL的宏,注意:消息路由开始要使用
BEGIN_MSG_MAP_EX,并且在预编译头文件中添加#include <atlcrack.h>
Message map macros
处理 WM_COMMAND
通知消息,可使用下面几个宏的一个:
- 为带特定的代码的特定控件处理通知。
- 处理特定代码的通知,而不管是哪个控件触发的。
- 处理特定控件的所有通知,而不管是哪个特定的通知。
- 处理ID范围内的控件的所有通知,而不管是哪个特定的通知。
- 处理ID范围内的控件的特定的通知。
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消息处理函数的原型:
void func ( UINT uCode, int nCtrlID, HWND hwndCtrl );
WM_COMMAND消息不使用返回值,因此其消息处理函数可以返回void。
WM_NOTIFY的消息处理函数的原型:
LRESULT func ( NMHDR* phdr );
消息处理函数的返回值用于消息的结果,这点不同于MFC,该处理函数接收LRESULT*数据并用于消息结果。通知标识和发送通知的控件句柄是NMHDR结构的成员。使用时需要将phdr转换为正确的类型。
实例中,我们在CMainDlg中添加列表框的LVN_ITEMCHANGED的通知消息的处理,用于在对话框中显示当前选择的元素:
class CMainDlg : public ...
{
BEGIN_MSG_MAP_EX(CMainDlg)
NOTIFY_HANDLER_EX(IDC_LIST, LVN_ITEMCHANGED, OnListItemchanged)
END_MSG_MAP()
LRESULT OnListItemchanged(NMHDR* phdr);
//...
};
LRESULT CMainDlg::OnListItemchanged ( NMHDR* phdr )
{
NMLISTVIEW* pnmlv = (NMLISTVIEW*) phdr;
int nSelItem = m_wndList.GetSelectedIndex();
CString sMsg;
// If no item is selected, show "none". Otherwise, show its index.
if ( -1 == nSelItem )
sMsg = _T("(none)");
else
sMsg.Format ( _T("%d"), nSelItem );
SetDlgItemText ( IDC_SEL_ITEM, sMsg );
return 0; // retval ignored
}
本例中并没有使用phdr,但是作为示范,我们将之转换为NMLISTVIEW*
类型数据。
Reflecting Notifications
如果一个从CWindowImpl派生的类实现一个控件,如之前的CEditImpl,那么我们就可以在该类中处理通知,而不是在父对话框中。这就叫做通知反射,它有些类似于MFC中的消息反射。不同点在于WTL中控件和父控件均参与反射,而MFC仅仅控件本身参与反射。
当你想将通知消息反射回控件类时,只需使用宏REFLECT_NOTIFICATIONS()
class CMainDlg : public ...
{
public:
BEGIN_MSG_MAP_EX(CMainDlg)
//...
NOTIFY_HANDLER_EX(IDC_LIST, LVN_ITEMCHANGED, OnListItemchanged)
REFLECT_NOTIFICATIONS()
END_MSG_MAP()
};
REFLECT_NOTIFICATIONS()在消息路由中添加一些代码,用于处理那些在之前的宏中没有被处理的任何通知消息。代码检查消息的
HWND并将消息转发的对应控件窗口类中。在OLE控件中,进行消息反射后,消息的值将会被改变,如使用OCM_xxx替换
WM_xxx,否则消息的处理与未反射的一样。
可被反射的消息有18种:
- 控件通知:
WM_COMMAND
,WM_NOTIFY
,WM_PARENTNOTIFY
所有者绘制:WM_DRAWITEM
,WM_MEASUREITEM
,WM_COMPAREITEM
,WM_DELETEITEM
- 列表框(List Box)的键盘消息:
WM_VKEYTOITEM
,WM_CHARTOITEM
- 其他:
WM_HSCROLL
,WM_VSCROLL
,WM_CTLCOLOR*
在控件类中,处理感兴趣的反射消息,然后在最后添加DEFAULT_REFLECTION_HANDLER()宏,
DEFAULT_REFLECTION_HANDLER()确保为处理的消息在
DefWindowProc()被正确的路由。下面是所有者绘制按钮的反射消息
WM_DRAWITEM的处理
class CODButtonImpl : public CWindowImpl<CODButtonImpl, CButton>
{
public:
BEGIN_MSG_MAP_EX(CODButtonImpl)
MSG_OCM_DRAWITEM(OnDrawItem)
DEFAULT_REFLECTION_HANDLER()
END_MSG_MAP()
void OnDrawItem ( UINT idCtrl, LPDRAWITEMSTRUCT lpdis )
{
// do drawing here...
}
};
WTL macros for handling reflected messages
WTL拥有MSG_OCM_*
的宏,用于其他17中可被反射的消息。既然WM_NOTIFY
和 WM_COMMAND的消息参数在使用时需要解码,WTL为之提供了特殊的宏
MSG_OCM_COMMAND
和MSG_OCM_NOTIFY。这些宏的使用类似
COMMAND_HANDLER_EX
和NOTIFY_HANDLER_EX,但是需要添加前缀
REFLECTED_:
class CMyTreeCtrl : public CWindowImpl<CMyTreeCtrl, CTreeViewCtrl>
{
public:
BEGIN_MSG_MAP_EX(CMyTreeCtrl)
REFLECTED_NOTIFY_CODE_HANDLER_EX(TVN_ITEMEXPANDING, OnItemExpanding)
DEFAULT_REFLECTION_HANDLER()
END_MSG_MAP()
LRESULT OnItemExpanding ( NMHDR* phdr );
};
处控件处理TVN_ITEMEXPANDING
消息。CMainDlg中的成员m_wndTree用DDX关联该树控件,并且在
CMainDlg中反射消息:
LRESULT CBuffyTreeCtrl::OnItemExpanding ( NMHDR* phdr )
{
NMTREEVIEW* pnmtv = (NMTREEVIEW*) phdr;
if ( pnmtv->action & TVE_COLLAPSE )
return TRUE; // don't allow it
else
return FALSE; // allow it
}
原文: WTL for MFC Programmers, Part IV - Dialogs and Controls