TN061:ON_NOTIFY 和 WM_NOTIFY 消息
注意
以下技术说明在首次包括在联机文档中后未更新。 因此,某些过程和主题可能已过时或不正确。 要获得最新信息,建议你在联机文档索引中搜索热点话题。
本技术说明提供了有关新 WM_NOTIFY 消息的背景信息,并描述了在 MFC 应用程序中处理 WM_NOTIFY 消息的推荐(也是最常见的)方法。
Windows 3.x 中的通知消息
在 Windows 3.x 中,控件通过向父级发送消息来通知其父级事件,例如鼠标单击、内容更改和选择更改,以及控件背景绘制。 简单的通知作为特殊的 WM_COMMAND 消息发送,通知代码(例如 BN_CLICKED)和控件 ID 被打包到 wParam 中,控件的句柄被打包到 lParam 中。 请注意,由于 wParam 和 lParam 已满,因此无法传递任何其他数据 - 这些消息只能是简单的通知。 例如,在 BN_CLICKED 通知中,无法发送有关单击按钮时鼠标光标位置的信息。
当 Windows 3.x 中的控件需要发送包含其他数据的通知消息时,它们会使用各种特殊用途的消息,包括 WM_CTLCOLOR、WM_VSCROLL、WM_HSCROLL、WM_DRAWITEM、WM_MEASUREITEM、WM_COMPAREITEM、WM_DELETEITEM、WM_CHARTOITEM、WM_VKEYTOITEM 等。 这些消息可以反射回发送它们的控件。 有关详细信息,请参阅 TN062:Windows 控件的消息反射。
Win32 中的通知消息
对于 Windows 3.1 中存在的控件,Win32 API 使用 Windows 3.x 中使用的大多数通知消息。 但是,Win32 还会向 Windows 3.x 中支持的控件添加一些复杂的控件。 通常,这些控件需要在通知消息中发送额外数据。 Win32 API 的设计者没有为每个需要额外数据的新通知添加新的 WM_ 消息,而是选择仅添加一条消息 WM_NOTIFY,它可以以标准化方式传递任意数量的额外数据*。
WM_NOTIFY 消息在 wParam 中包含发送消息的控件的 ID,在 lParam 中包含指向结构的指针。 该结构要么是 NMHDR 结构,要么是具有 NMHDR 结构作为其第一个成员的较大结构。 请注意,由于 NMHDR 是第一个成员,因此指向此结构的指针可用作指向 NMHDR 的指针,也可以用作指向较大结构的指针,具体取决于转换方式。
在大多数情况下,指针将指向较大结构,在使用时需要进行转换。 仅在少数通知中,例如公共通知(名称以 NM_ 开头)和工具提示控件的 TTN_SHOW 和 TTN_POP 通知,是实际使用的 NMHDR 结构。
NMHDR 结构或初始成员包含发送消息的控件的句柄和 ID 以及通知代码(例如 TTN_SHOW)。 NMHDR 结构的格式如下所示:
typedef struct tagNMHDR {
HWND hwndFrom;
UINT idFrom;
UINT code;
} NMHDR;
对于 TTN_SHOW 消息,code 成员将设置为 TTN_SHOW。
大多数通知传递一个指向较大结构的指针,该结构包含 NMHDR 结构作为其第一个成员。 例如,考虑列表视图控件的 LVN_KEYDOWN 通知消息使用的结构,该消息在列表视图控件中按下某个键时发送。 该指针指向 LV_KEYDOWN 结构,该结构定义如下:
typedef struct tagLV_KEYDOWN {
NMHDR hdr;
WORD wVKey;
UINT flags;
} LV_KEYDOWN;
请注意,由于 NMHDR 成员是此结构中的第一个成员,因此你在通知消息中传递的指针可以转换为指向 NMHDR 的指针或指向 LV_KEYDOWN 的指针。
所有新 Windows 控件的公共通知
所有新的 Windows 控件都有一些公共通知。 这些通知传递一个指向 NMHDR 结构的指针。
通知代码 | 发送原因 |
---|---|
NM_CLICK | 用户在控件中单击鼠标左键 |
NM_DBLCLK | 用户在控件中双击鼠标左键 |
NM_RCLICK | 用户在控件中单击鼠标右键 |
NM_RDBLCLK | 用户在控件中双击鼠标右键 |
NM_RETURN | 当控件具有输入焦点时用户按下 Enter 键 |
NM_SETFOCUS | 控件获得输入焦点 |
NM_KILLFOCUS | 控件失去输入焦点 |
NM_OUTOFMEMORY | 由于没有足够的可用内存,控件无法完成操作 |
ON_NOTIFY:在 MFC 应用程序中处理 WM_NOTIFY 消息
函数 CWnd::OnNotify
处理通知消息。 其默认实现检查消息映射,以获取要调用的通知处理程序。 一般情况下,不会重写 OnNotify
。 而是提供处理程序函数,并将该处理程序的消息映射条目添加到所有者窗口类的消息映射中。
通过 ClassWizard 属性表,ClassWizard 可以创建 ON_NOTIFY 消息映射条目,并提供框架处理程序函数。 有关使用 ClassWizard 简化此操作的详细信息,请参阅将消息映射到函数。
ON_NOTIFY 消息映射宏具有以下语法:
ON_NOTIFY(wNotifyCode, id, memberFxn)
参数的位置是:
wNotifyCode
要处理的通知消息的代码,例如 LVN_KEYDOWN。
id
为其发送通知的控件的子标识符。
memberFxn
发送此通知时调用的成员函数。
成员函数必须使用以下原型声明:
afx_msg void memberFxn(NMHDR* pNotifyStruct, LRESULT* result);
参数的位置是:
pNotifyStruct
指向通知结构的指针,如上一节所述。
result
指向将在返回之前设置的结果代码的指针。
示例
若要指定你希望成员函数 OnKeydownList1
处理来自 ID 为 IDC_LIST1
的 CListCtrl
的 LVN_KEYDOWN 消息,可以使用 ClassWizard 将以下内容添加到消息映射中:
ON_NOTIFY(LVN_KEYDOWN, IDC_LIST1, OnKeydownList1)
在上面的示例中,ClassWizard 提供的函数为:
void CMessageReflectionDlg::OnKeydownList1(NMHDR* pNMHDR, LRESULT* pResult)
{
LV_KEYDOWN* pLVKeyDow = (LV_KEYDOWN*)pNMHDR;
// TODO: Add your control notification handler
// code here
*pResult = 0;
}
请注意,ClassWizard 会自动提供正确类型的指针。 可以通过 pNMHDR 或 pLVKeyDow 访问通知结构。
ON_NOTIFY_RANGE
如果需要为一组控件处理相同的 WM_NOTIFY 消息,则可以使用 ON_NOTIFY_RANGE 而不是 ON_NOTIFY。 例如,你可能有一组按钮,你希望对某条通知消息执行相同的操作。
使用 ON_NOTIFY_RANGE 时,可以通过指定范围的开始和结束子标识符来指定一个连续的子标识符范围,以便处理通知消息。
ClassWizard 不处理 ON_NOTIFY_RANGE;若要使用它,需要自行编辑消息映射。
ON_NOTIFY_RANGE 的消息映射条目和函数原型如下所示:
ON_NOTIFY_RANGE(wNotifyCode, id, idLast, memberFxn)
参数的位置是:
wNotifyCode
要处理的通知消息的代码,例如 LVN_KEYDOWN。
id
连续标识符范围内的第一个标识符。
idLast
连续标识符范围内的最后一个标识符。
memberFxn
发送此通知时调用的成员函数。
成员函数必须使用以下原型声明:
afx_msg void memberFxn(UINT id, NMHDR* pNotifyStruct, LRESULT* result);
参数的位置是:
id
发送通知的控件的子标识符。
pNotifyStruct
指向通知结构的指针,如上一节所述。
result
指向将在返回之前设置的结果代码的指针。
ON_NOTIFY_EX、ON_NOTIFY_EX_RANGE
如果希望通知路由中的多个对象处理一条消息,则可以使用 ON_NOTIFY_EX(或 ON_NOTIFY_EX_RANGE),而不是 ON_NOTIFY(或 ON_NOTIFY_RANGE)。 EX 版本与普通版本的唯一区别是 EX 版本调用的成员函数返回一个 BOOL 值,指示是否应该继续处理消息。 从此函数返回 FALSE 允许你在多个对象中处理同一消息。
ClassWizard 不处理 ON_NOTIFY_EX 或 ON_NOTIFY_EX_RANGE;如果要使用其中任一项,则需要自行编辑消息映射。
ON_NOTIFY_EX 和 ON_NOTIFY_EX_RANGE 的消息映射条目和函数原型如下所示。 参数的含义与非 EX 版本的含义相同。
ON_NOTIFY_EX(nCode, id, memberFxn)
ON_NOTIFY_EX_RANGE(wNotifyCode, id, idLast, memberFxn)
上述两者的原型相同:
afx_msg BOOL memberFxn(UINT id, NMHDR* pNotifyStruct, LRESULT* result);
在这两种情况下,id 都包含发送通知的控件的子标识符。
如果已完全处理通知消息,则函数必须返回 TRUE;如果命令传送中的其他对象应有机会处理该消息,则必须返回 FALSE。