[源代码下载:
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
and
CTreeItem
(used together,
CTreeItem
wraps an
HTREEITEM
),
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基础函数
AtlInitCommonControls(),它封装了函数
InitCommonControlsEx()。接着,初始化全局
_Module,显示主对话框界面,通过DoModal()创建的ATL对话框实际上是模态的;而MFC中所有的对话框都是非模态的,MFC通过手工禁用对话框的父窗口来模拟模态的形式。最后,卸载
_Module
和COM,并把对话框的返回值作为程序退出码。
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按钮的消息将在
ALT_MSG_MAP(
1
)
中路由,所有发送到Cancel按钮的消息将在
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*类型,第二个是
ALT_MSG_MAP
段的索引。前者一般使用this,意思是要使用对话框自身的消息路由,后者告知该对象的消息要在哪个
ALT_MSG_MAP段中路由。
注意:如果使用VC 7.0 or 7.1 and WTL 7.0 or 7.1,以及以后的版本,如果一个由
CWindowImpl或
CDialogImpl
派生的类,做了以下几件事,运行时将会出现assert失败。
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 with
new
will not work. -
Transfers numerical data between an edit box and an
int
. -
Transfers numerical data between an edit box and an
unsigned
int
. -
Transfers numerical data between an edit box and a
float
or
double
. -
Transfers the state of a check box to/from an
int
or
bool
. -
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
or
bool
variable. The
int
version accepts/returns the values 0, 1, and 2 (or equivalently,
BST_UNCHECKED
,
BST_CHECKED
, and
BST_INDETERMINATE
). The
bool
version (added in WTL 7.1) can be used when a check box will never be in the indeterminate state; this version accepts/returns
true
if the check box is checked, or
false
if it’s unchecked. If the check box happens to be in the indeterminate state,
DDX_CHECK
returns
false
.
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 most
precision
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
的用法:
复选框永远不会处于不确定的状态,因此可以用一个bool型变量与之关联:
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
}