TN062: Message Reflection for Windows Controls
本技术文档解释了消息反射,MFC 4.0的新特性,并指导读者创建一个简单的、可重用的、使用了消息反射的控件。
本文并不讨论适用于ActiveX控件(通常称为OLE控件)的消息反射。请参看Visual C++ Programmer's Guide的ActiveX Controls: Subclassing a Windows Control一文。
什么是消息反射?Windows控件频繁地向其父窗口发送消息。例如,很多控件发送控件颜色提醒消息(WM_CTLCOLOR或其变种)给其父窗口,通知父窗口提供画刷,以重画控件的背景。
在4.0版本前的Windows和MFC中,父窗口,通常是一个对话框,负责处理这些消息。这意味着处理消息的代码放置在父窗口类中,并且需要在任何需要处理这个消息的窗口中重复写这些代码。在这种情况下,每个对话框都必须为其内部的控件定制背景色而处理提醒消息。若控件类可以自己处理其背景颜色,那么处理代码的重用就会变得非常容易了。
在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一般都可以正确地添加反射消息及其函数体。
参看TN061:ON_NOTIFY及WM_NOTIFY消息了解WM_NOTIFY消息。
反射消息的消息映射及处理函数原型要处理控件提醒消息的反射消息,使用下表列出的消息映射宏及函数原型。ClassWizard一般都可以正确地添加反射消息及其函数体。参看Visual C++ Programmer's Guide的Defining 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_REFLECT及ON_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(0, 0, 0);
m_clrBkgnd = RGB(255, 255, 0);
m_brBkgnd.CreateSolidBrush(m_clrBkgnd);
}
使用ClassWizard,在CYellowEdit里添加一个WM_CTLCOLOR反射消息的处理函数。注意,消息名前的等号表示此消息已被反射。这在Visual C++ Programmer's Guide的Defining 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的头文件。
编译并运行应用程序,文本框将有一个黄色的背景。