TN006:消息映射
本说明介绍 MFC 消息映射设施。
问题
Microsoft Windows 在使用其消息传递设施的窗口类中实现虚拟函数。 由于涉及大量消息,因此为每个 Windows 消息提供单独的虚拟函数会创建一个非常大的 vtable。
由于系统定义的 Windows 消息数随时间变化,并且应用程序可以定义自己的 Windows 消息,因此消息映射提供一定程度的间接性,以防止接口更改中断现有代码。
概述
MFC 提供了 switch 语句的替代方法,该语句在基于 Windows 的传统程序中用于处理发送到窗口的消息。 可以定义从消息到方法的映射,以便在窗口接收消息时自动调用相应的方法。 此消息映射设施设计为类似于虚拟函数,但具有 C++ 虚拟函数无法提供的其他优势。
定义消息映射
DECLARE_MESSAGE_MAP 宏声明类的三个成员。
由 AFX_MSGMAP_ENTRY 条目构成的专用数组,名为 _messageEntries。
一个名为 messageMap 的受保护 AFX_MSGMAP 结构,该结构指向 _messageEntries 数组。
一个名为
GetMessageMap
的受保护虚拟函数,该函数返回 messageMap 的地址。
应将此宏放入使用消息映射的任何类的声明中。 按照约定,它位于类声明的末尾。 例如:
class CMyWnd : public CMyParentWndClass
{
// my stuff...
protected:
//{{AFX_MSG(CMyWnd)
afx_msg void OnPaint();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
这是 AppWizard 和 ClassWizard 在创建新类时生成的格式。 ClassWizard 需要 //{{ 和 //}} 括号。
消息映射的表是使用一组宏来定义的,这些宏扩展到消息映射条目。 表以 BEGIN_MESSAGE_MAP 宏调用开始,该宏调用定义了由此消息映射处理的类以及未处理的消息传递到的父类。 该表以 END_MESSAGE_MAP 宏调用结尾。
在这两个宏调用之间是此消息映射要处理的每条消息的条目。 每条标准 Windows 消息都有一个格式为 ON_WM_MESSAGE_NAME 的宏,为该消息生成一个条目。
已定义标准函数签名,用于解压缩每个 Windows 消息的参数并提供类型安全性。 可以在 CWnd 声明的 Afxwin.h 文件中找到这些签名。 每个签名都标有关键字 afx_msg 以便于识别。
注意
ClassWizard 要求在消息映射处理程序声明中使用 afx_msg 关键字。
这些函数签名是使用简单约定派生的。 函数的名称始终以 "On
开头。 其后是 Windows 消息的名称(删除了“WM_”)和每个大写单词的第一个字母。 参数的顺序为 wParam,依次后跟 LOWORD
(lParam) 和 HIWORD
(lParam)。 不会传递未使用的参数。 MFC 类包装的任何句柄都转换为指向相应 MFC 对象的指针。 以下示例演示如何处理 WM_PAINT 消息并导致调用函数 CMyWnd::OnPaint
:
BEGIN_MESSAGE_MAP(CMyWnd, CMyParentWndClass)
//{{AFX_MSG_MAP(CMyWnd)
ON_WM_PAINT()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
必须在任何函数或类定义的范围之外定义消息映射表。 不应将其置于 extern "C" 块中。
注意
ClassWizard 将修改 //{{ 和 //}} 注释括号之间发生的消息映射条目。
用户定义的 Windows 消息
可以使用 ON_MESSAGE 宏将用户定义的消息包含在消息映射中。 此宏接受消息编号和以下形式的方法:
// inside the class declaration
afx_msg LRESULT OnMyMessage(WPARAM wParam, LPARAM lParam);
#define WM_MYMESSAGE (WM_USER + 100)
BEGIN_MESSAGE_MAP(CMyWnd, CMyParentWndClass)
ON_MESSAGE(WM_MYMESSAGE, OnMyMessage)
END_MESSAGE_MAP()
在此示例中,我们为自定义消息建立处理程序,该消息的 Windows 消息 ID 派生自用户定义消息的标准 WM_USER 基。 下面的示例演示如何调用此处理程序:
CWnd* pWnd = ...;
pWnd->SendMessage(WM_MYMESSAGE);
使用此方法的用户定义消息的范围必须为 WM_USER 到 0x7fff。
注意
ClassWizard 不支持从 ClassWizard 用户界面输入 ON_MESSAGE 处理程序例程。 必须从 Visual C++ 编辑器手动输入它们。 ClassWizard 将分析这些条目,并让你像浏览任何其他消息映射条目一样浏览这些条目。
已注册的 Windows 消息
RegisterWindowMessage 函数用于定义一个新的窗口消息,可保证该消息在整个系统中是唯一的。 ON_REGISTERED_MESSAGE 宏用于处理这些消息。 此宏接受包含已注册 Windows 消息 ID 的 UINT NEAR 变量的名称。 例如
class CMyWnd : public CMyParentWndClass
{
public:
CMyWnd();
//{{AFX_MSG(CMyWnd)
afx_msg LRESULT OnFind(WPARAM wParam, LPARAM lParam);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
static UINT NEAR WM_FIND = RegisterWindowMessage("COMMDLG_FIND");
BEGIN_MESSAGE_MAP(CMyWnd, CMyParentWndClass)
//{{AFX_MSG_MAP(CMyWnd)
ON_REGISTERED_MESSAGE(WM_FIND, OnFind)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
由于 ON_REGISTERED_MESSAGE 的实现方式,已注册 Windows 消息 ID 变量(此示例中为 WM_FIND)必须是 NEAR 变量。
使用此方法的用户定义消息的范围将为 0xC000 到 0xFFFF。
注意
ClassWizard 不支持从 ClassWizard 用户界面输入 ON_REGISTERED_MESSAGE 处理程序例程。 必须从文本编辑器手动输入它们。 ClassWizard 将分析这些条目,并让你像浏览任何其他消息映射条目一样浏览这些条目。
命令消息
菜单和加速器中的命令消息通过 ON_COMMAND 宏在消息映射中处理。 此宏接受命令 ID 和方法。 只有 wParam 等于指定命令 ID 的特定 WM_COMMAND 消息由消息映射条目中指定的方法处理。 命令处理程序成员函数不使用任何参数并返回 void
。 此宏采用以下格式:
ON_COMMAND(id, memberFxn)
命令更新消息通过同一机制路由,但改用 ON_UPDATE_COMMAND_UI 宏。 命令更新处理程序成员函数采用单个参数(指向 CCmdUI 对象的指针)并返回 void
。 此宏的格式为
ON_UPDATE_COMMAND_UI(id, memberFxn)
高级用户可以使用 ON_COMMAND_EX 宏,该宏是命令消息处理程序的扩展形式。 该宏提供 ON_COMMAND 功能的超集。 扩展的命令处理程序成员函数采用单个参数(一个包含命令 ID 的 UINT),并返回一个布尔值。 返回值应为 TRUE,指示已处理命令。 否则,将继续路由到其他命令目标对象。
这些形式的示例:
Resource.h 内(通常由 Visual C++ 生成)
#define ID_MYCMD 100 #define ID_COMPLEX 101
类声明内
afx_msg void OnMyCommand(); afx_msg void OnUpdateMyCommand(CCmdUI* pCmdUI); afx_msg BOOL OnComplexCommand(UINT nID);
消息映射定义内
ON_COMMAND(ID_MYCMD, OnMyCommand) ON_UPDATE_COMMAND_UI(ID_MYCMD, OnUpdateMyCommand) ON_COMMAND_EX(ID_MYCMD, OnComplexCommand)
实现文件中
void CMyClass::OnMyCommand() { // handle the command } void CMyClass::OnUpdateMyCommand(CCmdUI* pCmdUI) { // set the UI state with pCmdUI } BOOL CMyClass::OnComplexCommand(UINT nID) { // handle the command return TRUE; }
高级用户可以使用单个命令处理程序处理一系列命令:ON_COMMAND_RANGE 或 ON_COMMAND_RANGE_EX。 有关这些宏的详细信息,请参阅产品文档。
注意
ClassWizard 支持创建 ON_COMMAND 和 ON_UPDATE_COMMAND_UI 处理程序,但不支持创建 ON_COMMAND_EX 或 ON_COMMAND_RANGE 处理程序。 但是,类向导将分析所有四个命令处理程序变体,并可让你浏览这些变体。
控件通知消息
从子控件发送到窗口的消息在其消息映射条目中具有一个额外的信息:控件的 ID。 仅当以下条件为 true 时,才会调用消息映射条目中指定的消息处理程序:
控件通知代码(lParam 的高位字,如 BN_CLICKED)与消息映射条目中指定的通知代码匹配。
控件 ID (wParam) 与消息映射项中指定的控件 ID 匹配。
自定义控件通知消息可以使用 ON_CONTROL 宏通过自定义通知代码定义消息映射条目。 此宏的格式为
ON_CONTROL(wNotificationCode, id, memberFxn)
对于高级用法,ON_CONTROL_RANGE 可用于处理来自具有相同处理程序的一系列控件的特定控件通知。
注意
ClassWizard 不支持在用户界面中创建 ON_CONTROL 或 ON_CONTROL_RANGE 处理程序。 必须使用文本编辑器手动输入它们。 ClassWizard 将分析这些条目,并让你像浏览任何其他消息映射条目一样浏览这些条目。
Windows 公共控件使用功能更强大的 WM_NOTIFY 来处理复杂的控件通知。 此版本的 MFC 通过使用 ON_NOTIFY 和 ON_NOTIFY_RANGE 宏直接支持此新消息。 有关这些宏的详细信息,请参阅产品文档。