Restating the obvious about the WM_COMMAND message - The Old New Thing (microsoft.com)https://devblogs.microsoft.com/oldnewthing/20060302-10/?p=32093
Raymond Chen 2006年03月02日
关于 WM_COMMAND
消息的显而易见的知识点补充
简要
本文详细解释了
WM_COMMAND
消息的用途和参数,包括三种触发该消息的情景:菜单选择、控件通知和加速键击。wParam
参数的高字节表示通知代码,低字节表示控件或菜单项的标识符。lParam
参数是控件句柄,如果是菜单或加速器则为NULL
。文章还讨论了WM_NOTIFY
与WM_COMMAND
的区别,并指出在早期 Windows 版本中,由于WM_NOTIFY
不可用,控件通常使用WM_COMMAND
进行通知。最后,文中通过代码示例说明了如何手动生成这些消息。
正文
我对 MSDN 上关于 WM_COMMAND
消息的文档很满意,但为了更进一步的完整性,我将陈述一些显而易见的内容,希望你们,亲爱的读者,能够利用这些技术来填补MSDN其他部分的空白。
WM_COMMAND
消息的一行摘要说: “当用户从菜单中选择一个命令项,当一个控件向其父窗口发送一个通知消息,或者当一个加速键击被翻译时,会发送 WM_COMMAND
消息。”
简而言之,有三种情况会生成 WM_COMMAND
消息,即上述列出的三种。你应该将 WM_COMMAND
消息的菜单和加速器情况视为控件情况的特殊情况。
wParam
参数的高字节“如果消息来自控件,则指定通知代码。” 这里的“控件”是什么意思? 记住,你必须根据上下文来考虑事物。 WM_COMMAND
消息是在 Win32 的一般上下文中,特别是窗口管理器的上下文中提出的。 通常称为“控件”的窗口,如编辑框、按钮和列表框,以及“公共控件库”中的所有窗口类都是如此。 在窗口管理器的世界里,一个“控件”是一个窗口,其目的是为其父窗口提供一定程度的交互性(在静态控件的情况下,可能根本没有交互性)。
WM_COMMAND
主要用于对话框的上下文,进一步强调了这里的“控件”一词只是“子窗口”的同义词。
“通知代码”在这里意味着什么? 控件通知代码是由控件本身定义的任意16位值。 按照惯例,它们被命名为 xxN_xxxx
,其中“N”代表“通知”。 然而,要小心,不要将其与 WM_NOTIFY
消息相关联的通知代码混淆。 幸运的是,每个通知代码在其文档中都指定了它是作为 WM_COMMAND
通知还是 WM_NOTIFY
通知到达的。
现代控件设计师更有可能使用 WM_NOTIFY
通知,因为它们允许随通知传递额外的信息。 相比之下,WM_COMMAND
消息只传递通知本身;WM_COMMAND
消息的其他参数是强制的,如下所示。
如果 WM_NOTIFY
比 WM_COMMAND
更优越,为什么一些控件使用 WM_COMMAND
? 因为 WM_NOTIFY
直到 Windows 95 才可用。 在 Windows 95 之前编写的控件不得不满足于使用 WM_COMMAND
消息。
“如果消息来自加速器,这个值【wParam
参数的高字节】是 1。” 记住,我们仍然在窗口管理器的上下文中,特别是在 WM_COMMAND
消息的上下文中。 这里的加速器指的是在消息循环中调用 TranslateAccelerator
生成的消息。
“如果消息来自菜单,这个值是零。” 如果 WM_COMMAND
消息是由用户从菜单中选择一个项触发的,那么 wParam
的高字节是零。
wParam
参数的低字节“指定菜单项、控件或加速器的标识符。” 菜单项或加速器的标识符是你在菜单或加速器模板中与之关联的命令代码,或者(在菜单项的情况下)当你使用像 InsertMenuItem
这样的函数手动创建菜单项时。
你可能会将你的菜单项标识符和加速器标识符命名为
IDM_something
。) 控件的标识符由控件的创建者确定;回想一下,CreateWindow
和CreateWindowEx
函数的hMenu
参数如果你正在创建子窗口,它被视为子窗口标识符。 那就是控件标识符。 (你可以通过调用GetDlgCtrlID
函数来检索控件的标识符。
最后,lParam
参数是“如果消息来自控件,则为发送消息的控件的句柄。否则,此参数为 NULL。” 如果通知是由子窗口生成的(显然具有适合该子窗口的通知代码),那么该子窗口句柄将作为 lParam
传递。 如果通知是由加速器或菜单生成的,那么 lParam
是零。
注意,WM_COMMAND
消息的几乎所有参数一旦你决定了你要生成什么通知,都是强制的。
如果你正在从控件生成通知,你必须在 wParam
的高字节中传递通知代码,在 wParam
的低字节中传递控件标识符,并将控件句柄作为 lParam
传递。换句话说,一旦你决定 hwndC
窗口想要发送一个 CN_READY
通知,你别无选择,只能这样写:
SendMessage(GetParent(hwndC), WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(hwndC), CN_READY), (LPARAM)hwndC);
换句话说,所有控件通知都采用以下形式:
SendMessage(GetParent(hwndC), WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(hwndC), notificationCode), (LPARAM)hwndC);
其中 hwndC
是生成通知的控件,notificationCode
是通知代码。 当然,如果你更愿意发布通知而不是发送它,你可以使用 PostMessage
而不是 SendMessage
。
其他两种情况(加速器和菜单)通常不是你通常会编码的情况,因为你通常让 TranslateAccelerator
函数处理加速器,让菜单系统处理菜单标识符。 但是,如果出于某种原因,你想假装用户输入了一个加速器或选择了一个菜单项,你可以通过遵循文档中规定的规则手动生成通知。
// 模拟加速器 IDM_WHATEVER
SendMessage(hwnd, WM_COMMAND, MAKEWPARAM(IDM_WHATEVER, 1), 0);
在这里,hwnd
是你想要假装被传递到 TranslateAccelerator
函数的窗口,IDM_WHATEVER
是加速器标识符。
模拟菜单选择完全相同,只是(根据上述规则),你将 wParam
的高字节设置为零。
// 模拟菜单项 IDM_WHATEVER
SendMessage(hwnd, WM_COMMAND, MAKEWPARAM(IDM_WHATEVER, 0), 0);
在这里,hwnd
是与菜单关联的窗口。 一个窗口可以通过以下方式与菜单关联:通过菜单(通过将菜单句柄传递给 CreateWindow
或 CreateWindowEx
函数显式创建,或者通过在类注册中包含它隐式创建)或通过将菜单显式作为窗口参数传递给像 TrackPopupWindow
这样的函数。
加速器/菜单情况和控件通知情况之间的一个显著区别是,加速器和菜单标识符由调用应用程序定义,而控件通知由控件定义。
你可能已经注意到了利用控件通知代码的双关特性的机会。如果一个控件将通知代码定义为零,那么它将“看起来像”菜单项选择,因为在菜单项选择的情况下wParam
的高字节是零。
按钮控件利用了这个双关特性:
#define BN_CLICKED 0
这意味着,当用户点击按钮控件时,生成的WM_COMMAND
消息“闻起来像”菜单选择通知。你可能在没有意识到的情况下在你的对话框程序中利用了这一点。
静态控件也利用了这个双关特性:
#define STN_CLICKED 0
但是,为了使静态控件生成 STN_CLICKED
通知,你必须设置 SS_NOTIFY
样式。
我在开始时说,加速器和菜单情况只是控件情况的特殊情况。 如果你将 WM_COMMAND
消息的各个部分分开,你会发现它们分为两类:
- 发生了什么?(通知代码。)
- 发生在谁身上?(控件句柄和ID。)
在菜单或加速器的情况下,“发生了什么?”是“用户点击了菜单(0)”或“用户输入了加速器(1)”。 “发生在谁身上?”是“这个菜单ID”或“这个加速器ID”。 由于通知不是来自控件,控件句柄是 NULL
。
如果这对你们来是显而易见的, 我向 Win32 程序员道歉。
现在你已经是 WM_COMMAND
消息的专家了,也许你可以解决这个人的问题。