TN062:Windows 控件的消息反射

注意

以下技术说明在首次包括在联机文档中后未更新。 因此,某些过程和主题可能已过时或不正确。 要获得最新信息,建议你在联机文档索引中搜索热点话题。

本技术说明介绍消息反射,这是 MFC 4.0 中的新功能。 它还包含有关创建使用消息反射的简单可重用控件的指导。

本技术说明不讨论消息反射,因为它适用于 ActiveX 控件(以前称为 OLE 控件)。 请参阅 ActiveX 控件:创建 Windows 控件的子类一文。

什么是消息反射

Windows 控件经常将通知消息发送到其父窗口。 例如,许多控件会向其父级发送控件颜色通知消息(WM_CTLCOLOR 或其变体之一),以便父级可以提供画笔来绘制控件的背景。

在 Windows 和版本 4.0 之前的 MFC 中,父窗口(通常是对话框)负责处理这些消息。 这意味着用于处理消息的代码需要处于父窗口的类中,并且必须在需要处理该消息的每个类中重复。 在以上情况下,需要具有自定义背景的控件的每个对话框都必须处理控件颜色通知消息。 如果可以编写可处理其自己的背景色的控件类,则重用代码会容易得多。

在 MFC 4.0 中,旧机制仍然有效 — 父窗口可以处理通知消息。 但是除此之外,MFC 4.0 提供了一种名为“消息反射”的功能来方便进行重用,该功能允许在子控件窗口或父窗口或同时在两者中处理这些通知消息。 在控件背景色示例中,现在可以编写控件类以通过处理反射 WM_CTLCOLOR 消息来设置自己的背景色(完全不依赖于父级)。 (请注意,由于消息反射是由 MFC 实现,而不是由 Windows,因此父窗口类必须派生自 CWnd 才能使消息反射可正常工作。)

较旧版本的 MFC 会通过为一些消息(例如用于所有者绘制的列表框的消息(WM_DRAWITEM 等))提供虚拟函数来执行类似于消息反射的功能。 新的消息反射机制已通用化且一致。

消息反射与为 4.0 之前的 MFC 版本编写的代码向后兼容。

如果在父窗口的类中为特定消息或一系列消息提供了处理程序,则它会替代相同消息的反射消息处理程序,前提是你未在自己的处理程序中调用基类处理程序函数。 例如,如果在对话框类中处理 WM_CTLCOLOR,则处理会替代任何反射消息处理程序。

如果在父窗口类中,你为特定 WM_NOTIFY 消息或一系列 WM_NOTIFY 消息提供处理程序,则仅当发送这些消息的子控件没有通过 ON_NOTIFY_REFLECT() 实现的反射消息处理程序时,才会调用你的处理程序。 如果在消息映射中使用 ON_NOTIFY_REFLECT_EX(),则消息处理程序可能会也可能不会允许父窗口处理消息。 如果处理程序返回 FALSE,则消息也会由父级处理,而返回 TRUE 的调用不允许父级处理它。 请注意,反射消息在通知消息之前进行处理。

发送 WM_NOTIFY 消息时,控件会有机会首先处理它。 如果发送任何其他反射消息,则父窗口会有机会首先处理它,控件会收到反射消息。 为此,它需要处理程序函数以及控件类消息映射中的相应条目。

反射消息的消息映射宏与常规通知略有不同:它会将 _REFLECT 追加到其正常名称。 例如,若要在父级中处理 WM_NOTIFY 消息,请在父级的消息映射中使用 ON_NOTIFY 宏。 若要在子控件中处理反射消息,请在子控件的消息映射中使用 ON_NOTIFY_REFLECT 宏。 在某些情况下,参数也会不同。 请注意,ClassWizard 通常可以为你添加消息映射条目,并提供具有正确参数的主干函数实现。

有关新 WM_NOTIFY 消息的信息,请参阅 TN061:ON_NOTIFY 和 WM_NOTIFY 消息

反射消息的消息映射条目和处理程序函数原型

若要处理反射控件通知消息,请使用下表中列出的消息映射宏和函数原型。

ClassWizard 通常可以为你添加这些消息映射条目并提供主干函数实现。 有关如何为反射消息定义处理程序的信息,请参阅为反射消息定义消息处理程序

若要从消息名称转换为反射宏名称,请在名称前面附加 ON_ 并追加 _REFLECT。 例如,WM_CTLCOLOR 变为 ON_WM_CTLCOLOR_REFLECT。 (若要查看哪些消息可以反射,请对下表中的宏条目进行相反转换。)

以上规则的三个例外情况如下:

  • 用于 WM_COMMAND 通知的宏是 ON_CONTROL_REFLECT。

  • 用于 WM_NOTIFY 反射的宏是 ON_NOTIFY_REFLECT。

  • 用于 ON_UPDATE_COMMAND_UI 反射的宏是 ON_UPDATE_COMMAND_UI_REFLECT。

在以上每个特殊情况下,都必须指定处理程序成员函数的名称。 在其他情况下,必须为处理程序函数使用标准名称。

函数的参数和返回值的含义记录在函数名称或前面附加了 On 的函数名称下。 例如,CtlColor 记录在 OnCtlColor 中。 多个反射消息处理程序需要的参数比父窗口中的类似处理程序要少。 只需将下表中的名称与文档中的形参名称匹配即可。

映射条目 函数原型
ON_CONTROL_REFLECT(wNotifyCode,memberFxn) afx_msg voidmemberFxn( );
ON_NOTIFY_REFLECT(wNotifyCode,memberFxn) afx_msg voidmemberFxn( NMHDR*pNotifyStruct, LRESULT*result);
ON_UPDATE_COMMAND_UI_REFLECT(memberFxn) afx_msg voidmemberFxn( CCmdUI*pCmdUI);
ON_WM_CTLCOLOR_REFLECT( ) afx_msg HBRUSH CtlColor ( CDC*pDC, UINTnCtlColor);
ON_WM_DRAWITEM_REFLECT( ) afx_msg void DrawItem ( LPDRAWITEMSTRUCTlpDrawItemStruct);
ON_WM_MEASUREITEM_REFLECT( ) afx_msg void MeasureItem ( LPMEASUREITEMSTRUCTlpMeasureItemStruct);
ON_WM_DELETEITEM_REFLECT( ) afx_msg void DeleteItem ( LPDELETEITEMSTRUCTlpDeleteItemStruct);
ON_WM_COMPAREITEM_REFLECT( ) afx_msg int CompareItem ( LPCOMPAREITEMSTRUCTlpCompareItemStruct);
ON_WM_CHARTOITEM_REFLECT( ) afx_msg int CharToItem ( UINTnKey, UINTnIndex);
ON_WM_VKEYTOITEM_REFLECT( ) afx_msg int VKeyToItem ( UINTnKey, UINTnIndex);
ON_WM_HSCROLL_REFLECT( ) afx_msg void HScroll ( UINTnSBCode, UINTnPos);
ON_WM_VSCROLL_REFLECT( ) afx_msg void VScroll ( UINTnSBCode, UINTnPos);
ON_WM_PARENTNOTIFY_REFLECT( ) afx_msg void ParentNotify ( UINTmessage, LPARAMlParam);

ON_NOTIFY_REFLECT 和 ON_CONTROL_REFLECT 宏具有变体,使多个对象(如控件及其父级)可以处理给定消息。

映射条目 函数原型
ON_NOTIFY_REFLECT_EX(wNotifyCode,memberFxn) afx_msg BOOLmemberFxn( NMHDR*pNotifyStruct, LRESULT*result);
ON_CONTROL_REFLECT_EX(wNotifyCode,memberFxn) afx_msg BOOLmemberFxn( );

处理反射消息:可重用控件的示例

此简单示例创建一个名为 CYellowEdit 的可重用控件。 该控件的工作方式与常规编辑控件相同,只不过它会在黄色背景上显示黑色文本。 可以轻松添加成员函数,使 CYellowEdit 控件可以显示不同的颜色。

尝试创建可重用控件的示例

  1. 在现有应用程序中创建新对话框。 有关详细信息,请参阅对话框编辑器主题。

    必须具有可在其中开发可重用控件的应用程序。 如果没有现有应用程序可使用,请使用 AppWizard 创建基于对话框的应用程序。

  2. 将项目加载到 Visual C++ 中后,使用 ClassWizard 基于 CEdit 创建名为 CYellowEdit 的新类。

  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);
    }
    
  5. 使用 ClassWizard 将反射 WM_CTLCOLOR 消息的处理程序添加到 CYellowEdit 类。 请注意,可以处理的消息列表中消息名称前面的等号指示消息进行反射。 这在为反射消息定义消息处理程序中进行了介绍。

    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;
    }
    
  6. 使用以下代码替换函数的正文: 该代码指定文本颜色、文本背景色以及控件其余部分的背景色。

    pDC->SetTextColor(m_clrText);   // text
    pDC->SetBkColor(m_clrBkgnd);    // text bkgnd
    return m_brBkgnd;               // ctl bkgnd
    
  7. 在对话框中创建编辑控件,然后在按住 Ctrl 键的同时双击编辑控件以将它附加到成员变量。 在“添加成员变量”对话框中,完成变量名称,为类别选择“控件”,然后为变量类型选择“CYellowEdit”。 不要忘记在对话框中设置 Tab 键顺序。 此外,请确保在对话框的头文件中包含 CYellowEdit 控件的头文件。

  8. 生成并运行应用程序。 编辑控件会具有黄色背景。

另请参阅

按编号列出的技术说明
按类别列出的技术说明