在 WTL 中处理键盘和鼠标应用程序按钮






4.93/5 (22投票s)
2006年5月4日
11分钟阅读

122714

1409
如何处理键盘和鼠标上的额外应用程序按钮。
目录
引言
如今市面上销售的大多数键盘和鼠标都超越了标准的两个按钮和104个按键。从Windows Me和2000开始,操作系统就内置了对其中一些额外硬件按钮的支持(当然,更新的操作系统支持更多按钮),并会免费为应用程序提供一些有限的功能。例如,如果您按下“停止”按钮,当前应用程序将收到一个“Escape”键按下事件;滚动鼠标滚轮将滚动具有焦点的窗口(如果它是可滚动的);点击侧面的鼠标按钮可以像浏览器工具栏的“后退”和“前进”按钮一样导航IE。
应用程序可以自行处理这些按钮,以便提供自己的功能。例如,媒体播放器应用程序可以处理“播放”、“暂停”和“停止”按钮,并对当前歌曲执行相应的操作。图形应用程序可以处理鼠标滚轮消息以提供缩放功能。
示例项目是一个基于对话框的应用程序,它演示了如何处理应用程序按钮生成的消息;它只是简单地打印所有接收到的消息的详细信息。
本文假设您熟悉WTL的GUI编程。如果您不熟悉WTL或只需要复习,请参阅我的WTL系列。另外,如果您在编译时遇到问题,请参阅第一部分的README部分,其中有关于设置VC以使用WTL的说明。
硬件和驱动程序
由于我只使用过微软的键盘和鼠标,因此本文仅讨论微软硬件。来自其他公司的硬件(希望如此)在应用程序的视角来看应该表现相同,因为通知是通过窗口消息进行的。
Microsoft Natural Pro 键盘在功能键上方有一排蓝色按钮,如下图所示:
较新的键盘也将功能键用作应用程序按钮。例如,Natural Keyboard 4000 在 F1-F5 键上具有以下附加标签:
可以通过F Lock键在功能键和应用程序按钮之间切换。
鼠标有一个滚轮和两个额外的侧面按钮(有时每个侧面一个)。
能够正常工作并生成命令的按钮集**高度**依赖于您的操作系统和驱动程序版本。没有驱动程序的Windows 2000或更高版本只能识别一小部分按钮(主要是浏览器导航按钮和音量控制)。某些按钮只有在安装了IntelliType或IntelliPoint驱动程序后才能正常工作。例如,4000键盘的组合按钮和收藏夹按钮在没有IntelliType的情况下完全无法工作。
如果您的某些硬件按钮似乎不起作用,请记住这一点——更新您的驱动程序!
按钮识别
winuser.h 中为每个应用程序按钮定义了 #define
。鼠标按钮被称为“X按钮”,在消息中标识为 XBUTTON1
和 XBUTTON2
。这些按钮还有虚拟键码:VK_XBUTTON1
和 VK_XBUTTON2
。
滚轮充当鼠标中键。
由于滚轮按钮不被视为X按钮,因此在此不进行讨论。鼠标滚轮本身没有ID或虚拟键码;取而代之的是,当滚轮滚动时会发送一条特殊消息。
键盘按钮由以 APPCOMMAND_
开头的常量标识,例如 APPCOMMAND_BROWSER_BACKWARD
和 APPCOMMAND_VOLUME_MUTE
。请注意,某些键盘按钮根本没有ID,例如4000键盘的收藏夹按钮。
这些按钮调用的功能完全由IntelliType软件实现,应用程序在按下这些按钮时不会收到通知。
某些键盘具有缩放滑块。
此控件也完全由IntelliType处理。当用户移动滑块时,IntelliType会向活动应用程序发送一条鼠标滚轮消息,使其看起来像是用户按下了Control键滚动了滚轮。
处理应用程序按钮
当用户按下触发命令的鼠标或键盘按钮(并且该按钮不是完全由驱动程序实现的按钮,例如上面描述的收藏夹按钮)时,当前应用程序会收到一条 WM_APPCOMMAND
消息。WM_APPCOMMAND
消息将多条信息打包到消息参数中:事件发生的窗口句柄、命令ID(一个 APPCOMMAND_*
常量)、指示命令是由键盘还是鼠标触发的标志,以及指示同时按下了哪些Shift键和鼠标按钮的标志。
命令ID列表很长,因此在此不一一列出。请参阅MSDN关于WM_APPCOMMAND
的文档以获取完整列表。第二个标志可以是 FAPPCOMMAND_KEY
(如果命令是由键盘触发的)、FAPPCOMMAND_MOUSE
(如果命令是由鼠标触发的),或者 FAPPCOMMAND_OEM
(如果使用了其他方法)。您可以检查此标志,例如,以确定 APPCOMMAND_BROWSER_BACKWARD
命令是使用键盘上的Back按钮还是鼠标上的X按钮1调用的。
您可以检查最终的标志集,以确定生成命令时按下了哪些键或鼠标按钮。这些标志是:MK_CONTROL
、MK_SHIFT
、MK_LBUTTON
、MK_MBUTTON
、MK_RBUTTON
、MK_XBUTTON1
、MK_XBUTTON2
。请注意,驱动程序可能不会始终将这些标志传递给当前应用程序,因此,如果用户在按住Shift键的同时按下Back按钮,WM_APPCOMMAND
消息中发送的标志可能不包含 MK_SHIFT
。
需要注意的一点是,WM_APPCOMMAND
与大多数其他消息不同,应用程序应在处理该消息时返回 TRUE
(而不是零)。如果返回零,驱动程序可能会在您的应用程序执行操作之外执行默认命令。
在WTL中,可以使用消息映射宏 MSG_WM_APPCOMMAND
来处理 WM_APPCOMMAND
。您的处理程序应具有以下原型:
LRESULT OnAppCommand(HWND hwndCtrl, int nCommand, UINT uDevice, UINT uKeys);
如果消息已被处理,则函数应返回 TRUE
。在WTL 7.5之前的版本中,MSG_WM_APPCOMMAND
宏始终返回0,因此您需要重新定义它才能获得正确的行为:
#if _WTL_VER < 0x0750 #undef MSG_WM_APPCOMMAND #define MSG_WM_APPCOMMAND(func) \ if (uMsg == WM_APPCOMMAND) \ { \ SetMsgHandled(TRUE); \ lResult = func((HWND)wParam, GET_APPCOMMAND_LPARAM(lParam), \ GET_DEVICE_LPARAM(lParam), GET_KEYSTATE_LPARAM(lParam)); \ if(IsMsgHandled()) \ return TRUE; \ } #endif
这是示例应用程序在按下几个应用程序按钮后列出接收到的 WM_APPCOMMAND
消息的方式:
处理鼠标X按钮
客户区消息
当用户单击X按钮时,系统会向鼠标光标所在的窗口发送一条鼠标消息。当光标位于应用程序的客户区时,X按钮会生成 WM_XBUTTONDOWN
、WM_XBUTTONUP
和 WM_XBUTTONDBLCLK
消息。(当光标位于控件上方时,会改用 WM_APPCOMMAND
消息。)
X按钮鼠标消息在 WPARAM
中发送两个信息:一组标志,指示当时按下了哪些Shift键或其他鼠标按钮;以及一个数字,指示哪个鼠标按钮生成了消息(XBUTTON1
或 XBUTTON2
)。LPARAM
包含鼠标光标坐标,与其他鼠标消息相同。
在WTL中,每条消息都有一个消息映射宏。例如,MSG_WM_XBUTTONDOWN
处理 WM_XBUTTONDOWN
,其处理程序具有以下原型:
LRESULT OnXButtonDown(UINT uButton, UINT uKeys, CPoint pt);
与 WM_APPCOMMAND
类似,X按钮消息的处理程序应在处理消息后返回 TRUE
。WTL消息映射宏都返回零,因此您需要重新定义它们。例如,一个修复后的 MSG_WM_XBUTTONDOWN
是:
#undef MSG_WM_XBUTTONDOWN #define MSG_WM_XBUTTONDOWN(func) \ if (uMsg == WM_XBUTTONDOWN) \ { \ SetMsgHandled(TRUE); \ lResult = func(GET_XBUTTON_WPARAM(wParam), GET_KEYSTATE_WPARAM(wParam), \ CPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); \ if(IsMsgHandled()) \ return TRUE; \ }
MSG_WM_XBUTTONUP
和 MSG_WM_XBUTTONDBLCLK
也需要类似的更改。这需要对所有WTL版本进行,因为即使是7.5版本,这些宏仍然不正确。
非客户区消息
当用户在窗口的非客户区单击X按钮时,系统会向该窗口发送一条鼠标消息。这些消息是 WM_NCXBUTTONDOWN
、WM_NCXBUTTONUP
和 WM_NCXBUTTONDBLCLK
。这些消息在 WPARAM
中发送两个信息:一个命中测试值,指示光标位于非客户区的哪个部分;以及一个 XBUTTON*
常量,指示哪个按钮生成了消息。LPARAM
包含光标坐标。
WTL为这三个消息中的每一个都提供了一个消息映射宏,例如 MSG_WM_NCXBUTTONDOWN
处理 WM_NCXBUTTONDOWN
。处理程序应具有以下原型:
LRESULT OnNCXButtonDown(UINT uButton, int nHitTest, CPoint pt);
与 WM_XBUTTON*
消息类似,WM_NCXBUTTON*
的处理程序在处理消息后应返回 TRUE
。WTL为 WM_NCXBUTTON*
提供的宏都返回零,因此您需要重新定义它们。例如,一个修复后的 MSG_WM_NCXBUTTONDOWN
是:
#undef MSG_WM_NCXBUTTONDOWN #define MSG_WM_NCXBUTTONDOWN(func) \ if (uMsg == WM_NCXBUTTONDOWN) \ { \ SetMsgHandled(TRUE); \ lResult = func(GET_XBUTTON_WPARAM(wParam), GET_NCHITTEST_WPARAM(wParam), \ CPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); \ if(IsMsgHandled()) \ return TRUE; \ }
MSG_WM_NCXBUTTONUP
和 MSG_WM_NCXBUTTONDBLCLK
也需要类似的更改。这需要对所有WTL版本进行,因为即使是7.5版本,这些宏仍然不正确。
这是单击X按钮后收到的鼠标消息示例:
处理鼠标滚轮事件
有两种类型的滚轮。原始类型在旋转时会咔哒咔哒地卡入不同的位置,而新型号则平滑旋转。IntelliMouse Explorer 4.0 具有一个平滑滚动的滚轮,它也是一个倾斜滚轮:
倾斜功能将在本节后面讨论。平滑滚动和倾斜功能需要IntelliPoint;没有它,滚轮将像旧式点击式滚轮一样发送消息。
当用户滚动滚轮时,应用程序会收到 WM_MOUSEWHEEL
消息。WPARAM
包含两部分信息:指示按下了哪些Shift键或鼠标按钮的标志,以及一个距离值。LPARAM
包含光标的坐标。
点击式滚轮的距离值始终是 WHEEL_DELTA
(定义为120)的倍数。此值的符号告诉您滚轮的旋转方向:正值表示向前(远离用户),负值表示向后(朝向用户)。例如,如果距离是 -WHEEL_DELTA
,则用户将滚轮向自己方向滚动了一格。如果距离是 3*WHEEL_DELTA
,则用户快速向远离自己的方向滚动了三格。较大的距离值意味着应用程序应该执行更大的滚动或缩放操作。
这是滚动点击式滚轮时收到的消息示例:
使用平滑滚动的滚轮时,距离值不总是 WHEEL_DELTA
的倍数。应用程序负责跟踪已覆盖的距离。如果滚轮的移动距离不足 WHEEL_DELTA
的整数倍,应用程序可以执行较小的滚动或缩放,或者等待直到达到 WHEEL_DELTA
的整数倍。
这是使用平滑滚动滚轮时收到的消息示例:
如果键盘有缩放滑块,IntelliType会发送带有 MK_CONTROL
标志的 WM_MOUSEWHEEL
消息。如果您的应用程序具有缩放功能,您应该响应此消息执行缩放操作。上面截图中的最后两条消息是用缩放滑块生成的——注意消息表明Control键被按下。4000键盘上的缩放滑块是一个简单的数字开关,因此距离参数始终是 WHEEL_DELTA
。如果距离为正,应用程序应放大;如果为负,应用程序应缩小。
请注意,某些应用程序,特别是IE和Firefox,缩放方向是相反的。4000键盘的缩放滑块上标有+和-符号,表示向上推滑块(发送正距离)会放大,向下推会缩小。我个人会遵循滑块的标记。此外,Office和Paint Shop Pro中的缩放功能与滑块标记一致。希望有一天,所有程序都能和谐一致,决定缩放的方向。
倾斜滚轮并未被任何已发布的Windows版本原生支持,因此IntelliPoint会将倾斜滚轮事件转换为水平滚动消息,应用程序不会直接收到倾斜滚轮事件的通知。在Vista中,有一个新消息 WM_MOUSEHWHEEL
,在滚轮倾斜时发送。WM_MOUSEHWHEEL
的参数与 WM_MOUSEWHEEL
相同,但距离参数指示左右移动而不是前后移动。此外,WM_MOUSEHWHEEL
的处理程序在处理消息后应返回 TRUE
,而 WM_MOUSEWHEEL
的处理程序应返回零。
在WTL中,您可以使用 MSG_WM_MOUSEWHEEL
消息映射宏来处理 WM_MOUSEWHEEL
。处理程序应具有以下原型:
LRESULT OnMouseWheel(UINT uKeys, short nDistance, CPoint pt);
WTL尚未支持 WM_MOUSEHWHEEL
,但创建其消息映射宏很简单:
#define WM_MOUSEHWHEEL 0x020E #define MSG_WM_MOUSEHWHEEL(func) \ if (uMsg == WM_MOUSEHWHEEL) \ { \ SetMsgHandled(TRUE); \ lResult = func((UINT)LOWORD(wParam), (short)HIWORD(wParam), \ CPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); \ if(IsMsgHandled()) \ return TRUE; \ }
WM_MOUSEHWHEEL
处理程序应具有与上面 OnMouseWheel()
相同的原型。请注意,我尚未在Vista上测试过示例代码,但希望一切都能正常工作!
版权和许可
本文是受版权保护的材料,©2006 Michael Dunn。我意识到这无法阻止人们在网上到处复制,但我还是必须说。如果您有兴趣翻译本文,请给我发电子邮件告知。我不认为会拒绝任何人翻译的许可,只是想了解翻译情况,以便在此处发布链接。
本文附带的演示代码已发布到公共领域。我以这种方式发布是为了让代码能够造福所有人。(我没有将文章本身设为公共领域,因为文章仅在CodeProject上提供有助于我个人的可见性和CodeProject网站。)如果您在自己的应用程序中使用演示代码,发送电子邮件告知我将不胜感激(只是为了满足我对大家是否从我的代码中受益的好奇心),但这不是必需的。在您自己的源代码中注明出处也同样受到赞赏,但不是必需的。
修订历史
2006年5月4日:文章首次发布。