(转)WTL入门(4)--- 对话框和控件

 [源代码下载: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:CStaticCButtonCListBox,CComboBox,CEdit,CScrollBar,CDragListBox
        2)Common controls:CImageListCListViewCtrl (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中的公共控件:CPagerCtrlCFlatScrollBarCLinkCtrl (可点击的超链接,在XP系统及其之后有效)
        4)WTL特有的封装类:CBitmapButtonCCheckListViewCtrl (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

创建一个模态对话框,过程略。

程序的入口函数代码:

[cpp]  view plain copy
  1. int WINAPI _tWinMain (   
  2.     HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/,   
  3.     LPTSTR lpstrCmdLine, int nCmdShow )  
  4. {  
  5.     HRESULT hRes = ::CoInitialize(NULL);  
  6.    
  7.     AtlInitCommonControls(ICC_COOL_CLASSES | ICC_BAR_CLASSES);  
  8.    
  9.     hRes = _Module.Init(NULL, hInstance);  
  10.    
  11.     int nRet = 0;  
  12.     // BLOCK: Run application  
  13.     {  
  14.         CMainDlg dlgMain;  
  15.         nRet = dlgMain.DoModal();  
  16.     }  
  17.    
  18.     _Module.Term();  
  19.     ::CoUninitialize();  
  20.     return nRet;  
  21. }  

这段代码首先使用单线程套间初始化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() 的调用参数需要改变。比如,可以改为:

[cpp]  view plain copy
  1. AtlInitCommonControls ( ICC_WIN95_CLASSES );  

这会注册一些不需要的控件,但是,当我们再往对话框中添加其他一些不同类型的控件时,我们就不需要再添加相应的ICC_*常量了。

Using the Control Wrapper Classes

有几种方法可以使控件和成员变量关联起来。一些空间使用CWindow或其他窗体接口类(如CListViewCtrl),而其他是CWindowImpl的派生类。如果仅仅需要一个临时变量,使用CWindow对象就可以了,但是如果需要子类化控件并且向其传递消息,就需要使用CWindowImpl。

ATL Way 1 - Attaching a CWindow

这是最简单的方法,声明一个CWindow或其他窗体类,通过Attach方法或CWindow的构造函数或分配运算关联变量和控件窗口句柄。

[cpp]  view plain copy
  1. HWND hwndList = GetDlgItem(IDC_LIST);  
  2. CListViewCtrl wndList1 (hwndList);  // use constructor  
  3. CListViewCtrl wndList2, wndList3;  
  4.    
  5. wndList2.Attach ( hwndList );     // use Attach method  
  6. 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 成员变量

[cpp]  view plain copy
  1. class CMainDlg : public CDialogImpl<CMainDlg>  
  2. {  
  3. // ...  
  4. protected:  
  5.     CContainedWindow m_wndOKBtn, m_wndExitBtn;  
  6. };  

第二,添加ALT_MSG_MAP消息路由段,OK按钮使用1号段,Cancel使用2号段,这意味着所有发送到OK按钮的消息将在ALT_MSG_MAP(1)中路由,所有发送到Cancel按钮的消息将在ALT_MSG_MAP(2)中路由。

[cpp]  view plain copy
  1. class CMainDlg : public CDialogImpl<CMainDlg>  
  2. {  
  3. public:  
  4.     BEGIN_MSG_MAP_EX(CMainDlg)  
  5.         MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)  
  6.         COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)  
  7.         COMMAND_ID_HANDLER(IDOK, OnOK)  
  8.         COMMAND_ID_HANDLER(IDCANCEL, OnCancel)  
  9.     ALT_MSG_MAP(1)  
  10.         MSG_WM_SETCURSOR(OnSetCursor_OK)  
  11.     ALT_MSG_MAP(2)  
  12.         MSG_WM_SETCURSOR(OnSetCursor_Exit)  
  13.     END_MSG_MAP()  
  14.    
  15.     LRESULT OnSetCursor_OK(HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg);  
  16.     LRESULT OnSetCursor_Exit(HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg);  
  17. };  

第三步:在CMainDlg的构造函数中,调用 CContainedWindow 的构造函数并告知使用哪个ALT_MSG_MAP 段。

[cpp]  view plain copy
  1. CMainDlg::CMainDlg() : m_wndOKBtn(this, 1),   
  2.                        m_wndExitBtn(this, 2)  
  3. {  
  4. }  

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成员关联一个控件:

[cpp]  view plain copy
  1. LRESULT CMainDlg::OnInitDialog(...)  
  2. {  
  3. // ...  
  4.     // Attach CContainedWindows to OK and Exit buttons  
  5.     m_wndOKBtn.SubclassWindow ( GetDlgItem(IDOK) );  
  6.     m_wndExitBtn.SubclassWindow ( GetDlgItem(IDCANCEL) );  
  7.    
  8.     return TRUE;  
  9. }  

下面是WM_SETCURSOR的消息处理:

[cpp]  view plain copy
  1. LRESULT CMainDlg::OnSetCursor_OK (  
  2.     HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg )  
  3. {  
  4. static HCURSOR hcur = LoadCursor ( NULL, IDC_HAND );  
  5.    
  6.     if ( NULL != hcur )  
  7.         {  
  8.         SetCursor ( hcur );  
  9.         return TRUE;  
  10.         }  
  11.     else  
  12.         {  
  13.         SetMsgHandled(false);  
  14.         return FALSE;  
  15.         }  
  16. }  
  17.    
  18. LRESULT CMainDlg::OnSetCursor_Exit (  
  19.     HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg )  
  20. {  
  21. static HCURSOR hcur = LoadCursor ( NULL, IDC_NO );  
  22.    
  23.     if ( NULL != hcur )  
  24.         {  
  25.         SetCursor ( hcur );  
  26.         return TRUE;  
  27.         }  
  28.     else  
  29.         {  
  30.         SetMsgHandled(false);  
  31.         return FALSE;  
  32.         }  
  33. }  

注:如果想使用CButton的特性和方法,可以这样定义成员变量:

[cpp]  view plain copy
  1. CContainedWindowT<CButton> m_wndOKBtn;  

ATL Way 3 - Subclassing

第三种方法需要创建CWindowImpl的派生类并用之子类化控件。这很像第二种方法,但是消息的处理放在了CWindowImpl的派生类中。
例子中的About 按钮就是使用子类化的方法:首先为该控件创建一个CButtonImpl类,它从CWindowImpl派生并处理消息WM_SETCURSOR。

[cpp]  view plain copy
  1. class CButtonImpl : public CWindowImpl<CButtonImpl, CButton>  
  2. {  
  3.     BEGIN_MSG_MAP_EX(CButtonImpl)  
  4.         MSG_WM_SETCURSOR(OnSetCursor)  
  5.     END_MSG_MAP()  
  6.    
  7.     LRESULT OnSetCursor(HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg)  
  8.     {  
  9.     static HCURSOR hcur = LoadCursor ( NULL, IDC_SIZEALL );  
  10.    
  11.         if ( NULL != hcur )  
  12.             {  
  13.             SetCursor ( hcur );  
  14.             return TRUE;  
  15.             }  
  16.         else  
  17.             {  
  18.             SetMsgHandled(false);  
  19.             return FALSE;  
  20.             }  
  21.     }  
  22. };  

然后,在对话框类中创建CButtonImpl类型的成员变量:

[cpp]  view plain copy
  1. class CMainDlg : public CDialogImpl<CMainDlg>  
  2. {  
  3. // ...  
  4. protected:  
  5.     CButtonImpl m_wndAboutBtn;  
  6. };  

最后在OnInitDialog()中子类化这个控件:

[cpp]  view plain copy
  1. LRESULT CMainDlg::OnInitDialog(...)  
  2. {  
  3.     // ...  
  4.    
  5.     // CButtonImpl: subclass the About button  
  6.     m_wndAboutBtn.SubclassWindow ( GetDlgItem(ID_APP_ABOUT) );  
  7.    
  8.     return TRUE;  
  9. }  

WTL Way 1 - DDX_CONTROL

WTL的对话框数据变化机制很像MFC中的DDX。这同样需要为控件创建CWindowImpl的派生类。为了使用DDX,需要在头文件中添加

[cpp]  view plain copy
  1. #include <atlddx.h>  

首先,为使用DDX,需要在对话框的继承列表中添加CWinDataExchange

[cpp]  view plain copy
  1. class CMainDlg : public CDialogImpl<CMainDlg>,   
  2.                  public CWinDataExchange<CMainDlg>  
  3. {  
  4. //...  
  5. };  

然后,创建一个控件的派生类并定义一个成员变量,然后像MFC一样,创建一个DDX路由表

[cpp]  view plain copy
  1. <strong>class CEditImpl : public CWindowImpl<CEditImpl, CEdit>  
  2. {  
  3.     BEGIN_MSG_MAP_EX(CEditImpl)  
  4.         MSG_WM_CONTEXTMENU(OnContextMenu)  
  5.     END_MSG_MAP()  
  6.    
  7.     void OnContextMenu ( HWND hwndCtrl, CPoint ptClick )  
  8.     {  
  9.         MessageBox("Edit control handled WM_CONTEXTMENU");  
  10.     }  
  11. };</strong>  
  12.    
  13. class CMainDlg : public CDialogImpl<CMainDlg>,   
  14.                  public CWinDataExchange<CMainDlg>  
  15. {  
  16. //...  
  17.    
  18.    <strong> BEGIN_DDX_MAP(CMainDlg)  
  19.         DDX_CONTROL(IDC_EDIT, m_wndEdit)  
  20.     END_DDX_MAP()</strong>  
  21.    
  22. protected:  
  23.     CContainedWindow m_wndOKBtn, m_wndExitBtn;  
  24.     CButtonImpl m_wndAboutBtn;  
  25.     <strong>CEditImpl   m_wndEdit;</strong>  
  26. };  

最后,在OnInitDialog()中,调用从CWinDataExchange继承来的方法DoDataExchange()。第一次调用DoDataExchange()时,将会对控件进行子类化,并关联控件和成员变量。

[cpp]  view plain copy
  1. LRESULT CMainDlg::OnInitDialog(...)  
  2. {  
  3. // ...  
  4.     // Attach CContainedWindows to OK and Exit buttons  
  5.     m_wndOKBtn.SubclassWindow ( GetDlgItem(IDOK) );  
  6.     m_wndExitBtn.SubclassWindow ( GetDlgItem(IDCANCEL) );  
  7.    
  8.     // CButtonImpl: subclass the About button  
  9.     m_wndAboutBtn.SubclassWindow ( GetDlgItem(ID_APP_ABOUT) );  
  10.    
  11.     // First DDX call, hooks up variables to controls.  
  12.     <strong>DoDataExchange(false);</strong>  
  13.    
  14.     return TRUE;  
  15. }  

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的派生类:

[cpp]  view plain copy
  1. #define DDX_CONTROL_IMPL(x) \  
  2.     class x##_ddx : public CWindowImpl<x##_ddx, x> \  
  3.         { public: DECLARE_EMPTY_MSG_MAP() };  

然后使用

[cpp]  view plain copy
  1. DDX_CONTROL_IMPL(CListViewCtrl)  

就可以创建一个从CWindowImpl派生的CListViewCtrl_ddx类,它可以用于DDX_CONTROL。

More on DDX

WTL支持文本框和字符串变量之间的数据交换,也可以解析字符串为一个数值,并可以转换为整数或浮点数。WTL也支持复选框和多选框的状态和整型变量间的交换。

DDX macros

DDX_TEXT
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.
DDX_INT
Transfers numerical data between an edit box and an  int.
DDX_UINT
Transfers numerical data between an edit box and an  unsignedint.
DDX_FLOAT
Transfers numerical data between an edit box and a  float or double.
DDX_CHECK
Transfers the state of a check box to/from an  int or bool.
DDX_RADIO
Transfers the state of a group of radio buttons to/from an  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_CHECKreturnsfalse.

There is also an additional floating-point macro that was added in WTL 7.1:

DDX_FLOAT_P(controlID, variable, precision)
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 和 DDX_FLOAT_P 时,需要添加宏定义

[cpp]  view plain copy
  1. #define _ATL_USE_DDX_FLOAT  

默认情况下,为了优化,是禁止浮点数支持的。

More about DoDataExchange()

可以像使用MFC中的UpdateData()一样使用DoDataExchange(),它的原型:

[cpp]  view plain copy
  1. BOOL DoDataExchange ( BOOL bSaveAndValidate = FALSE,   
  2.                       UINT nCtlID = (UINT)-1 );  
bSaveAndValidate
标志位,指示数据传输的方向. 如果是TRUE,表示数据从控件传递交换到变量中;否则表示数据从变量交换到控件中。注意:此参数默认值为FALSE,而MFC中的UpdateData()的参数的默认值是TRUE。我们同样可是使用宏DDX_SAVE和DDX_LOAD作为参数,这样方便记忆。
nCtlID
如果为-1,表示更新所有控件的数据;否则如果仅仅想更新某个控件的数据,将该控件的ID作为此参数。

DoDataExchange()如果执行成功,返回TRUE,否则返回FALSE。在对话框类中,有两个函数可以重载用于错误处理。1)OnDataExchangeError(),当不管以任何原因导致数据交换失败时,都会被调用。CWinDataExchange中的默认处理办法是:发出一个蜂鸣音,并将导致出错的控件获得焦点。2)OnDataValidateError(),用于数据有效性检测。

Using DDX

首先用DDX在CMainDlg中添加一对变量

[cpp]  view plain copy
  1. class CMainDlg : public ...  
  2. {  
  3. //...  
  4.     BEGIN_DDX_MAP(CMainDlg)  
  5.         DDX_CONTROL(IDC_EDIT, m_wndEdit)  
  6.         <strong>DDX_TEXT(IDC_EDIT, m_sEditContents)  
  7.         DDX_INT(IDC_EDIT, m_nEditNumber)</strong>  
  8.     END_DDX_MAP()  
  9.    
  10. protected:  
  11.     // DDX variables  
  12.     <strong>CString m_sEditContents;  
  13.     int     m_nEditNumber;</strong>  
  14. };  

在“确定”按钮的处理中,首先调用DoDataExchange(),将文本框中的字符串转换成刚刚添加的两个变量。将结果显示在列表框中。

[cpp]  view plain copy
  1. LRESULT CMainDlg::OnOK ( UINT uCode, int nID, HWND hWndCtl )  
  2. {  
  3. CString str;  
  4.    
  5.     // Transfer data from the controls to member variables.  
  6.     if ( !DoDataExchange(true) )  
  7.         return;  
  8.    
  9.     m_wndList.DeleteAllItems();  
  10.    
  11.     m_wndList.InsertItem ( 0, _T("DDX_TEXT") );  
  12.     m_wndList.SetItemText ( 0, 1, m_sEditContents );  
  13.    
  14.     str.Format ( _T("%d"), m_nEditNumber );  
  15.     m_wndList.InsertItem ( 1, _T("DDX_INT") );  
  16.     m_wndList.SetItemText ( 1, 1, str );  
  17. }  


此时,如果在文本框中输入非数值文本,DDX_INT 将会失败,并调用OnDataExchangeError(),重载本函数

[cpp]  view plain copy
  1. void CMainDlg::OnDataExchangeError ( UINT nCtrlID, BOOL bSave )  
  2. {  
  3. CString str;  
  4.    
  5.     str.Format ( _T("DDX error during exchange with control: %u"), nCtrlID );  
  6.     MessageBox ( str, _T("ControlMania1"), MB_ICONWARNING );  
  7.        
  8.     ::SetFocus ( GetDlgItem(nCtrlID) );  
  9. }  



下面,添加一个复选框,来演示DDX_CHECK的用法:

复选框永远不会处于不确定的状态,因此可以用一个bool型变量与之关联:
[cpp]  view plain copy
  1. class CMainDlg : public ...  
  2. {  
  3. //...  
  4.     BEGIN_DDX_MAP(CMainDlg)  
  5.         DDX_CONTROL(IDC_EDIT, m_wndEdit)  
  6.         DDX_TEXT(IDC_EDIT, m_sEditContents)  
  7.         DDX_INT(IDC_EDIT, m_nEditNumber)  
  8.        <strong> DDX_CHECK(IDC_SHOW_MSG, m_bShowMsg)</strong>  
  9.     END_DDX_MAP()  
  10.    
  11. protected:  
  12.     // DDX variables  
  13.     CString m_sEditContents;  
  14.     int     m_nEditNumber;  
  15.     <strong>bool    m_bShowMsg;</strong>  
  16. };  
在OnOK的最后,添加m_bShowMsg的测试代码:
[cpp]  view plain copy
  1. void CMainDlg::OnOK ( UINT uCode, int nID, HWND hWndCtl )  
  2. {  
  3.     // Transfer data from the controls to member variables.  
  4.     if ( !DoDataExchange(true) )  
  5.         return;  
  6. //...  
  7.     if ( m_bShowMsg )  
  8.         MessageBox ( _T("DDX complete!"), _T("ControlMania1"),   
  9.                      MB_ICONINFORMATION );  
  10. }  

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 通知消息,可使用下面几个宏的一个:

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)
处理ID范围内的控件的所有通知,而不管是哪个特定的通知。
COMMAND_RANGE_CODE_HANDLER_EX(idFirst, idLast, code, func)
处理ID范围内的控件的特定的通知。

例如:
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消息处理函数的原型:

[cpp]  view plain copy
  1. void func ( UINT uCode, int nCtrlID, HWND hwndCtrl );  

WM_COMMAND消息不使用返回值,因此其消息处理函数可以返回void。
WM_NOTIFY的消息处理函数的原型:

[cpp]  view plain copy
  1. LRESULT func ( NMHDR* phdr );  

消息处理函数的返回值用于消息的结果,这点不同于MFC,该处理函数接收LRESULT*数据并用于消息结果。通知标识和发送通知的控件句柄是NMHDR结构的成员。使用时需要将phdr转换为正确的类型。
实例中,我们在CMainDlg中添加列表框的LVN_ITEMCHANGED的通知消息的处理,用于在对话框中显示当前选择的元素:

[cpp]  view plain copy
  1. class CMainDlg : public ...  
  2. {  
  3.     BEGIN_MSG_MAP_EX(CMainDlg)  
  4.        <strong> NOTIFY_HANDLER_EX(IDC_LIST, LVN_ITEMCHANGED, OnListItemchanged)</strong>  
  5.     END_MSG_MAP()  
  6.    
  7.     <strong>LRESULT OnListItemchanged(NMHDR* phdr);</strong>  
  8. //...  
  9. };  
  10. <strong>LRESULT CMainDlg::OnListItemchanged ( NMHDR* phdr )  
  11. {  
  12. NMLISTVIEW* pnmlv = (NMLISTVIEW*) phdr;  
  13. int nSelItem = m_wndList.GetSelectedIndex();  
  14. CString sMsg;  
  15.    
  16.     // If no item is selected, show "none". Otherwise, show its index.  
  17.     if ( -1 == nSelItem )  
  18.         sMsg = _T("(none)");  
  19.     else  
  20.         sMsg.Format ( _T("%d"), nSelItem );  
  21.    
  22.     SetDlgItemText ( IDC_SEL_ITEM, sMsg );  
  23.     return 0;   // retval ignored  
  24. }</strong>  

本例中并没有使用phdr,但是作为示范,我们将之转换为NMLISTVIEW* 类型数据。

Reflecting Notifications

如果一个从CWindowImpl派生的类实现一个控件,如之前的CEditImpl,那么我们就可以在该类中处理通知,而不是在父对话框中。这就叫做通知反射,它有些类似于MFC中的消息反射。不同点在于WTL中控件和父控件均参与反射,而MFC仅仅控件本身参与反射。
当你想将通知消息反射回控件类时,只需使用宏REFLECT_NOTIFICATIONS()

[cpp]  view plain copy
  1. class CMainDlg : public ...  
  2. {  
  3. public:  
  4.     BEGIN_MSG_MAP_EX(CMainDlg)  
  5.         //...  
  6.         NOTIFY_HANDLER_EX(IDC_LIST, LVN_ITEMCHANGED, OnListItemchanged)  
  7. <strong>        REFLECT_NOTIFICATIONS()</strong>  
  8.     END_MSG_MAP()  
  9. };  

REFLECT_NOTIFICATIONS()在消息路由中添加一些代码,用于处理那些在之前的宏中没有被处理的任何通知消息。代码检查消息的HWND并将消息转发的对应控件窗口类中。在OLE控件中,进行消息反射后,消息的值将会被改变,如使用OCM_xxx替换WM_xxx,否则消息的处理与未反射的一样。

可被反射的消息有18种:

  • 控件通知: WM_COMMANDWM_NOTIFYWM_PARENTNOTIFY
  • 所有者绘制:WM_DRAWITEMWM_MEASUREITEMWM_COMPAREITEMWM_DELETEITEM
  • 列表框(List Box)的键盘消息: WM_VKEYTOITEMWM_CHARTOITEM
  • 其他:WM_HSCROLLWM_VSCROLLWM_CTLCOLOR*

在控件类中,处理感兴趣的反射消息,然后在最后添加DEFAULT_REFLECTION_HANDLER()宏,DEFAULT_REFLECTION_HANDLER()确保为处理的消息在DefWindowProc()被正确的路由。下面是所有者绘制按钮的反射消息WM_DRAWITEM的处理

[cpp]  view plain copy
  1. class CODButtonImpl : public CWindowImpl<CODButtonImpl, CButton>  
  2. {  
  3. public:  
  4.     BEGIN_MSG_MAP_EX(CODButtonImpl)  
  5.        <strong> MSG_OCM_DRAWITEM(OnDrawItem)  
  6.         DEFAULT_REFLECTION_HANDLER()</strong>  
  7.     END_MSG_MAP()  
  8.    
  9.    <strong> void OnDrawItem ( UINT idCtrl, LPDRAWITEMSTRUCT lpdis )  
  10.     {  
  11.         // do drawing here...  
  12.     }</strong>  
  13. };  
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_:

[cpp]  view plain copy
  1. class CMyTreeCtrl : public CWindowImpl<CMyTreeCtrl, CTreeViewCtrl>  
  2. {  
  3. public:  
  4.  BEGIN_MSG_MAP_EX(CMyTreeCtrl)  
  5.  <strong> REFLECTED_NOTIFY_CODE_HANDLER_EX(TVN_ITEMEXPANDING, OnItemExpanding)  
  6.   DEFAULT_REFLECTION_HANDLER()</strong>  
  7.  END_MSG_MAP()  
  8.    
  9.  LRESULT OnItemExpanding ( NMHDR* phdr );  
  10. };  

处控件处理TVN_ITEMEXPANDING 消息。CMainDlg中的成员m_wndTree用DDX关联该树控件,并且在CMainDlg中反射消息:

[cpp]  view plain copy
  1. LRESULT CBuffyTreeCtrl::OnItemExpanding ( NMHDR* phdr )  
  2. {  
  3. NMTREEVIEW* pnmtv = (NMTREEVIEW*) phdr;  
  4.    
  5.     if ( pnmtv->action & TVE_COLLAPSE )  
  6.         return TRUE;    // don't allow it  
  7.     else  
  8.         return FALSE;   // allow it  
  9. }  

 

原文: WTL for MFC Programmers, Part IV - Dialogs and Controls

转自:http://blog.csdn.net/wcyoot/article/details/6674808

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值