65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (22投票s)

2006年5月4日

11分钟阅读

viewsIcon

122714

downloadIcon

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按钮”,在消息中标识为 XBUTTON1XBUTTON2。这些按钮还有虚拟键码:VK_XBUTTON1VK_XBUTTON2

滚轮充当鼠标中键。

由于滚轮按钮不被视为X按钮,因此在此不进行讨论。鼠标滚轮本身没有ID或虚拟键码;取而代之的是,当滚轮滚动时会发送一条特殊消息。

键盘按钮由以 APPCOMMAND_ 开头的常量标识,例如 APPCOMMAND_BROWSER_BACKWARDAPPCOMMAND_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_CONTROLMK_SHIFTMK_LBUTTONMK_MBUTTONMK_RBUTTONMK_XBUTTON1MK_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_XBUTTONDOWNWM_XBUTTONUPWM_XBUTTONDBLCLK 消息。(当光标位于控件上方时,会改用 WM_APPCOMMAND 消息。)

X按钮鼠标消息在 WPARAM 中发送两个信息:一组标志,指示当时按下了哪些Shift键或其他鼠标按钮;以及一个数字,指示哪个鼠标按钮生成了消息(XBUTTON1XBUTTON2)。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_XBUTTONUPMSG_WM_XBUTTONDBLCLK 也需要类似的更改。这需要对所有WTL版本进行,因为即使是7.5版本,这些宏仍然不正确。

非客户区消息

当用户在窗口的非客户区单击X按钮时,系统会向该窗口发送一条鼠标消息。这些消息是 WM_NCXBUTTONDOWNWM_NCXBUTTONUPWM_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_NCXBUTTONUPMSG_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日:文章首次发布。

© . All rights reserved.