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

 [源代码下载:http://download.csdn.net/source/3522801]

MFC中,对话框和控件的封装节省了我们大量的时间和成本,否则我们需要编写大量的消息处理来管理各个控件。MFC提供了Dialog Data Exchange(DDX,对话框数据交换,对话框和变量之间的数据传输)的功能。WTL同样支持这些特性,并且在一写公共控件的封装类中做了一些改进。本文用一个给予对话框的程序来展示过去使用的MFC的一些特性,以及WTL的一些消息处理的增强功能,对于高级UI特性和WTL中新增的控件,下章将会讲到。

Refresher on ATL Dialogs

ATL有两个对话框类,CDialogImplCAxDialogImpl,后者用于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基础函数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
{
// ...
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
{
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 m_wndOKBtn;

ATL Way 3 - Subclassing

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

class CButtonImpl : public CWindowImpl
{
    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
{
// ...
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 

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

class CMainDlg : public CDialogImpl, 
                 public CWinDataExchange
{
//...
};

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

class CEditImpl : public CWindowImpl
{
    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, 
                 public CWinDataExchange
{
//...
 
    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 \
        { public: DECLARE_EMPTY_MSG_MAP() };

然后使用

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, 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:

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_FLOATDDX_FLOAT_P 时,需要添加宏定义

#define _ATL_USE_DDX_FLOAT

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

More about DoDataExchange()

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

BOOL DoDataExchange ( BOOL bSaveAndValidate = FALSE, 
                      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中添加一对变量

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

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

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
{
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_COMMANDMSG_OCM_NOTIFY。这些宏的使用类似COMMAND_HANDLER_EXNOTIFY_HANDLER_EX,但是需要添加前缀REFLECTED_:

class CMyTreeCtrl : public CWindowImpl
{
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

你可能感兴趣的:(ATL/WTL)