Rover12421's Blog

The End.

[翻译,MSDN]TN062:Windows控件消息反射

TN062Windows控件消息反射

TN062: Message Reflection for Windows Controls

本技术文档解释了消息反射,MFC 4.0的新特性,并指导读者创建一个简单的、可重用的、使用了消息反射的控件。

本文并不讨论适用于ActiveX控件(通常称为OLE控件)的消息反射。请参看Visual C++ Programmer's GuideActiveX Controls: Subclassing a Windows Control一文。

什么是消息反射?

Windows控件频繁地向其父窗口发送消息。例如,很多控件发送控件颜色提醒消息(WM_CTLCOLOR或其变种)给其父窗口,通知父窗口提供画刷,以重画控件的背景。

4.0版本前的WindowsMFC中,父窗口,通常是一个对话框,负责处理这些消息。这意味着处理消息的代码放置在父窗口类中,并且需要在任何需要处理这个消息的窗口中重复写这些代码。在这种情况下,每个对话框都必须为其内部的控件定制背景色而处理提醒消息。若控件类可以自己处理其背景颜色,那么处理代码的重用就会变得非常容易了。

MFC 4.0中,旧的机制仍将正常运行,即父窗口可以处理提醒消息。但同时,MFC 4.0提供了消息反射,允许这些提醒消息既可在子控件中处理又可在其父窗口中处理,使得代码重用更加容易。以控件背景颜色为例,现在可以创建一个控件,自己来处理WM_CTLCOLOR的反射消息,一切都不用依靠其父窗口。(注意,消息反射是由MFC实现的,而非Windows,因此要使用消息反射,父窗口必须继承于CWnd

旧版本的MFC为一些消息提供了一些虚函数,使得消息的处理与消息反射机制相似。例如自画列表框(owner-drawn list boxes)的消息(WM_DRAWITEM之类)。新的消息反射机制是通用并且一致的。

消息映射向后兼容MFC 4.0之前的版本。

若在父窗口提供了某一个或是一组指定消息的处理函数,它将覆盖反射的消息处理函数。在自己的消息处理函数中不应调用父窗口的处理函数。例如: 若在对话框处理WM_CTLCOLOR消息,则此处理函数将会覆盖所有反射消息的处理函数。

若在父窗口类中提供了某一个或一组WM_NOTIFY消息,则消息处理函数仅在子窗口没有使用ON_NOTIFY_REFLECT()处理反射消息时被调用。若在消息映射中使用ON_NOTIFY_REFLECT_EX(),消息句柄可能会或可能不会允许父窗口处理这些消息。若处理函数返回TRUE,则父窗口也会处理此消息,而返回FALSE,则不允许父窗口处理。注意,反射消息在提醒消息前被处理。

WM_NOTIFY被发送时,首先反射给控件来处理。而其他消息被发送时,父窗口将首先处理,然后子控件才会接收到反射的消息。要让子控件处理这些反射的消息,需要一个消息处理函数,及消息映射实体。

反射消息的消息映射宏与普通的提醒消息稍有不同:它在普通的名字后加上了_REFLECT。例如,要在父窗口处理WM_NOTIFY消息,需要在父窗口的消息映射中加上ON_NOTIFY。而要在子控件里处理反射消息,则需要在子控件里使用ON_NOTIFY_REFLECT宏。在一些情况下,参数也有可能不同。注意,ClassWizard一般都可以正确地添加反射消息及其函数体。

参看TN061ON_NOTIFYWM_NOTIFY消息了解WM_NOTIFY消息。

反射消息的消息映射及处理函数原型

要处理控件提醒消息的反射消息,使用下表列出的消息映射宏及函数原型。ClassWizard一般都可以正确地添加反射消息及其函数体。参看Visual C++ Programmer's GuideDefining a Message Handler for a Reflected Message,以了解如何定义反射消息的处理函数。

要将消息名转化为反射宏名,在消息名前面加上ON_,并在其后面加上_REFLECT即可。例如:WM_CTLCOLOR的反射宏名为ON_WM_CTLCOLOR_REFLECT。(要查看哪些消息可以被反射,将下表的宏作逆变换。)

这种命名规则的特例有以下三个:

l        WM_COMMAND的反射宏为ON_CONTROL_REFLECT

l        WM_NOTIFY的反射宏为ON_NITIFY_REFLECT

l        ON_UPDATE_COMMAND_UI的反射宏为ON_UPDATE_COMMAND_UI_REFLECT

在上面三种特例下,必须指定处理函数名,而在其它情况下,必须使用标准的处理函数名。

函数参数及返回值的意义归档在其函数名,或函数名前加On下。例如CtlColor被归档在OnCtlColor中。一些反射消息的处理函数需要的参数比父窗口消息处理函数的更少。直接将本表中的名字与文档中的形参名相对应即可。

映射实体

函数原型

ON_CONTROL_REFLECT(wNotifyCode, memberFxn)

afx_msg void memberFxn();

ON_NOTIFY_REFLECT(wNotifyCode, memberFxn)

afx_msg void memberFxn(NMHDR * pNotifyStruct, LRESULT* result);

ON_UPDATE_COMMAND_UI_REFLECT(memberFxn)

afx_msg void memberFxn(CCmdUI* pCmdUI);

ON_WM_CTLCOLOR_REFLECT()

afx_msg HBRUSH CtlColor(CDC* pDC, UINT nCtlColor);

ON_WM_DRAWITEM_REFLECT()

afx_msg void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);

ON_WM_MEASUREITEM_REFLECT()

afx_msg void MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct);

ON_WM_DELETEITEM_REFLECT()

afx_msg void DeleteItem(LPDELETEITEMSTRUCT lpDeleteItemStruct);

ON_WM_COMPAREITEM_REFLECT()

afx_msg int CompareItem(LPCOMPAREITEMSTRUCT lpCompareItemStruct);

ON_WM_CHARTOITEM_REFLECT()

afx_msg int CharToItem(UINT nKey, UINT nIndex);

ON_WM_VKEYTOITEM_REFLECT()

afx_msg int VKeyToItem(UINT nKey, UINT nIndex);

ON_WM_HSCROLL_REFLECT()

afx_msg void HScroll(UINT nSBCode, UINT nPos);

ON_WM_VSCROLL_REFLECT()

afx_msg void VScroll(UINT nSBCode, UINT nPos);

ON_WM_PARENTNOTIFY_REFLECT()

afx_msg void ParentNotify(UINT message, LPARAM lParam);

ON_NOTIFY_REFLECTON_CONTROL_REFLECT宏有以下变异体,允许其被多个对象处理(例如控件及其父窗口)。

映射实体

函数原型

ON_NOTIFY_REFLECT_EX(wNotifyCode, memberFxn)

afx_msg BOOL memberFxn(NMHDR * pNotifyStruct, LRESULT* result);

ON_CONTROL_REFLECT_EX(wNotifyCode, memberFxn)

afx_msg BOOL memberFxn();

处理反射消息: 一个重用控件的例子

本例创建了一个可重用的控件CYellowEdit。此控件与普通的文本框相似,只是它在黄色背景上显示黑色的文字。很容易为CYellowEdit添加成员函数,以使之显示不同的背景颜色。

实现步骤如下:

1.      在工程中创建一个新的对话框。请参见Visual C++ User’s Guide以了解更多信息。

为开发一个可重用的控件,应有一个应用程序。若没有现成的应用程序,使用AppWizard创建一个基于对话框的应用程序。

2.      加载工程后,使用ClassWizard创建一个继承于CEdit的新类CYellowEdit。选定“Add to Component Gallery”复选框。

3.      CYellowEdit类里添加三个变量。前两个变量为COLORREF类型,以存放文本颜色及背景颜色。第三个变量是一个CBrush对象,以存放背景画刷。CBrush可以只创建一次,之后就直接引用,并且在CYellowEdit被销毁的时候自动销毁。

4.      在构造函数里初始化成员变量:

CYellowEdit::CYellowEdit()

{

    m_clrText = RGB(, , );

    m_clrBkgnd = RGB(255, 255, );

    m_brBkgnd.CreateSolidBrush(m_clrBkgnd);

}

使用ClassWizard,在CYellowEdit里添加一个WM_CTLCOLOR反射消息的处理函数。注意,消息名前的等号表示此消息已被反射。这在Visual C++ Programmer's GuideDefining a Message Handler for a Reflected Message有相应描述。

ClassWizard将添加以下的消息映射宏及函数:

ON_WM_CTLCOLOR_REFLECT()

 

// Note: other code will be in between....

 

HBRUSH CYellowEdit::CtlColor(CDC* pDC, UINT nCtlColor)

{

    // TODO: Change any attributes of the DC here

   

    // TODO: Return a non-NULL brush if the

    //    parent's handler should not be called

    return NULL;

}

用下面的代码替换函数体。这些代码指定了文字颜色,文字背景颜色以及控件其余部分的背景颜色。

pDC->SetTextColor(m_clrText);    // text

pDC->SetBkColor(m_clrBkgnd);    // text bkgnd

return m_brBkgnd;                // ctl bkgnd

在对话框里创建一个文本框,按住CTRL键的同时双击文本框控件,以将其与一成员变量绑定。在Add Member Variable对话框中,输入变量名,选择控件,并选择变量类型为CYellowEdit。不要忘了设置对话框的TAB顺序。并且要在对话框的头文件中包含CYellowEdit的头文件。

编译并运行应用程序,文本框将有一个黄色的背景。

现在可以使用Component GalleryCYellowEdit控件类添加到其它工程中去了。

Comments