鼠标输入概述
鼠标是应用程序的重要但可选的用户输入设备。 一个编写良好的应用程序应该包括一个鼠标界面,但它不应该仅仅依赖鼠标来获取用户输入。 应用程序还应提供完整的键盘支持。
应用程序接收以消息形式发送或发布到其窗口的鼠标输入。
本部分涵盖了以下主题:
鼠标光标
当用户移动鼠标时,系统将在屏幕上移动一个名为鼠标光标的位图。 鼠标光标包含一个称为热点的单个像素点,系统会跟踪并识别该点作为光标的位置。 当鼠标事件发生时,包含热点的窗口通常会接收到该事件产生的鼠标消息。 窗口无需处于活动状态或具有键盘焦点即可接收鼠标消息。
系统维护一个控制鼠标速度的变量,即用户移动鼠标时光标移动的距离。 可以将 SystemParametersInfo 函数与 SPI_GETMOUSE 或 SPI_SETMOUSE 标志一起使用,以检索或设置鼠标速度。 有关鼠标光标的详细信息,请参阅光标。
鼠标捕获
当鼠标事件发生时,系统通常会在包含光标热点的窗口中发布鼠标消息。 应用程序可以通过使用 SetCapture 函数将鼠标消息路由到特定窗口来更改此行为。 该窗口接收所有鼠标消息,直到应用程序调用 ReleaseCapture 函数或指定另一个捕获窗口,或者直到用户单击由另一个线程创建的窗口。
当鼠标捕获发生变化时,系统会向丢失鼠标捕获的窗口发送一条 WM_CAPTURECHANGED 消息。 消息的 lParam 参数指定了获取鼠标捕获的窗口的句柄。
只有前台窗口可以捕获鼠标输入。 当后台窗口试图捕获鼠标输入时,它只会接收光标热点位于窗口可见部分时发生的鼠标事件的消息。
如果窗口必须接收所有鼠标输入,即使光标移动到窗口外,捕获鼠标输入也很有用。 例如,应用程序通常会在鼠标按下事件后跟踪光标位置,一直跟踪光标直到鼠标按下事件发生。 如果应用程序没有捕获鼠标输入,并且用户在窗口外释放鼠标按钮,则窗口不会收到按钮松开消息。
线程可以使用 GetCapture 函数来确定其某个窗口是否捕获了鼠标。 如果线程的某个窗口捕获了鼠标,则 GetCapture 会检索该窗口的句柄。
鼠标 ClickLock
鼠标 ClickLock 辅助功能允许用户在单击后锁定主鼠标按钮。 对于应用程序,按钮似乎仍被按下。 若要解锁按钮,应用程序可以发送任何鼠标消息,或者用户可以单击任何鼠标按钮。 此功能允许用户更简单地进行复杂的鼠标组合。 例如,那些有某些身体局限的人可以更容易地突出显示文本、拖动对象或打开菜单。 有关详细信息,请参阅以下标志和 SystemParametersInfo 中的备注:
- SPI_GETMOUSECLICKLOCK
- SPI_SETMOUSECLICKLOCK
- SPI_GETMOUSECLICKLOCKTIME
- SPI_SETMOUSECLICKLOCKTIME
鼠标配置
虽然鼠标是应用程序的重要输入设备,但并非每个用户都必须拥有鼠标。 应用程序可以通过将 SM_MOUSEPRESENT 值传递给 GetSystemMetrics 函数来确定系统是否包含鼠标。
Windows 支持最多有三个按钮的鼠标。 在三键鼠标上,按钮被指定为左、中、右按钮。 与鼠标按钮相关的消息和命名常量使用字母 L、M 和 R 来标识按钮。 单键鼠标上的按钮被认为是左键。 尽管 Windows 支持具有多个按钮的鼠标,但大多数应用程序主要使用左键,而其他按钮则很少使用。
应用程序还可以支持鼠标滚轮。 可以按下或旋转鼠标滚轮。 按下鼠标滚轮时,它将充当中间(第三个)按钮,向应用程序发送正常的中间按钮消息。 旋转时,会向应用程序发送滚轮消息。 有关详细信息,请参阅鼠标滚轮部分。
应用程序可以支持应用程序命令按钮。 这些按钮称为 X 按钮,旨在方便访问 Internet 浏览器、电子邮件和媒体服务。 当按下 X 按钮时,会向应用程序发送一条 WM_APPCOMMAND 消息。 有关详细信息,请参阅 WM_APPCOMMAND 消息中的说明。
应用程序可以通过将 SM_CMOUSEBUTTONS 值传递给 GetSystemMetrics 函数来确定鼠标上的按钮数量。 要为左撇子用户配置鼠标,应用程序可以使用 SwapMouseButton 函数来颠倒鼠标左键和右键的含义。 将 SPI_SETMOUSEBUTTONSWAP 值传递给 SystemParametersInfo 函数是颠倒按钮含义的另一种方法。 但是请注意,鼠标是一种共享资源,因此颠倒按钮的含义会影响所有应用程序。
XBUTTONs
Windows 支持具有五个按钮的鼠标。 除了左、中和右按钮外,还有 XBUTTON1 和 XBUTTON2,使用浏览器时提供向后和向前导航。
窗口管理器通过 WM_XBUTTON* 和 WM_NCXBUTTON* 消息支持 XBUTTON1 和 XBUTTON2。 这些消息中 WPARAM 的 HIWORD 包含一个标志,指示按下了哪个 X 按钮。 由于这些鼠标消息也适用于常量 WM_MOUSEFIRST 和 WM_MOUSELAST 之间,因此应用程序可以使用 GetMessage 或 PeekMessage 筛选所有鼠标消息。
以下支持 XBUTTON1 和 XBUTTON2:
- WM_APPCOMMAND
- WM_NCXBUTTONDBLCLK
- WM_NCXBUTTONDOWN
- WM_NCXBUTTONUP
- WM_XBUTTONDBLCLK
- WM_XBUTTONDOWN
- WM_XBUTTONUP
- MOUSEHOOKSTRUCTEX
修改了以下 API 以支持这些按钮:
组件应用程序中的子窗口不太可能能够直接实现 XBUTTON1 和 XBUTTON2 的命令。 因此,当单击 X 按钮时,DefWindowProc 会向窗口发送 WM_APPCOMMAND 消息。 DefWindowProc 会将 WM_APPCOMMAND 消息发送到其父窗口。 这类似于右键单击调用上下文菜单的方式,即 DefWindowProc 向菜单发送 WM_CONTEXTMENU 消息,并将其发送给其父级。 此外,如果 DefWindowProc 收到顶级窗口的 WM_APPCOMMAND 消息,它将调用代码为 HSHELL_APPCOMMAND 的 shell 挂钩。
支持带有额外按键的键盘,用于浏览器功能、媒体功能、应用程序启动和电源管理。 有关详细信息,请参阅用于浏览和其他功能的键盘键。
鼠标消息
当用户移动鼠标或按下或释放鼠标按钮时,鼠标会生成一个输入事件。 系统将鼠标输入事件转换为消息,并将其发布到相应线程的消息队列中。 当鼠标消息的发布速度超过线程的处理速度时,系统会丢弃除最新鼠标消息外的所有消息。
当光标在窗口边界内发生鼠标事件时,或者当窗口捕获鼠标时,窗口会收到鼠标消息。 鼠标消息分为两组:工作区消息和非工作区消息。 通常,应用程序处理工作区消息并忽略非工作区消息。
本部分涵盖了以下主题:
工作区鼠标消息
当鼠标事件发生在窗口的工作区内时,窗口会接收工作区鼠标消息。 当用户在工作区内移动光标时,系统会向窗口发布 WM_MOUSEMOVE 消息。 当用户在光标位于工作区内时按下或释放鼠标按钮时,它会发布以下消息之一。
消息 | 含义 |
---|---|
WM_LBUTTONDBLCLK | 双击鼠标左键。 |
WM_LBUTTONDOWN | 按下了鼠标左键。 |
WM_LBUTTONUP | 释放了鼠标左键。 |
WM_MBUTTONDBLCLK | 双击鼠标中键。 |
WM_MBUTTONDOWN | 按下了鼠标中键。 |
WM_MBUTTONUP | 释放了鼠标中键。 |
WM_RBUTTONDBLCLK | 双击鼠标右键。 |
WM_RBUTTONDOWN | 按下了鼠标右键。 |
WM_RBUTTONUP | 释放了鼠标右键。 |
WM_XBUTTONDBLCLK | 双击 X 鼠标按钮。 |
WM_XBUTTONDOWN | 按下了 X 鼠标按钮。 |
WM_XBUTTONUP | 释放了 X 鼠标按钮。 |
此外,应用程序可以调用 TrackMouseEvent 函数,让系统发送另外两条消息。 当光标在工作区上悬停一段时间后,它会发布 WM_MOUSEHOVER 消息。 当光标离开工作区时,它会发布 WM_MOUSELEAVE 消息。
消息参数
工作区鼠标消息的 lParam 参数指示光标热点的位置。 低序词表示热点的 x 坐标,高序词表示 y 坐标。 坐标在工作区坐标中指定。 在工作区坐标系中,屏幕上的所有点都是相对于工作区左上角的坐标 (0,0) 指定的。
wParam 参数包含指示鼠标事件发生时其他鼠标按钮以及 CTRL 和 SHIFT 键状态的标志。 当鼠标消息处理取决于另一个鼠标按钮或 CTRL 或 SHIFT 键的状态时,你可以检查这些标志。 wParam 参数可以是以下值的组合。
值 | 说明 |
---|---|
MK_CONTROL | 按下了 CTRL 键。 |
MK_LBUTTON | 按下了鼠标左键。 |
MK_MBUTTON | 按下了鼠标中键。 |
MK_RBUTTON | 按下了鼠标右键。 |
MK_SHIFT | 按下了 SHIFT 键。 |
MK_XBUTTON1 | 按下了第一个 X 按钮。 |
MK_XBUTTON2 | 按下了第二个 X 按钮。 |
双击消息
当用户快速连续点击鼠标按钮两次时,系统会生成一条双击消息。 当用户单击按钮时,系统将建立一个以光标热点为中心的矩形。 它还标记单击发生的时间。 当用户第二次单击同一按钮时,系统会确定热点是否仍在矩形内,并计算自第一次单击以来经过的时间。 如果热点仍在矩形内,并且经过的时间未超过双击超时值,系统将生成双击消息。
应用程序可以使用 GetDoubleClickTime 和 SetDoubleClickTime 函数分别获取和设置双击超时值。 或者,应用程序可以通过将 SPI_SETDOUBLECLICKTIME 标志与 SystemParametersInfo 函数结合使用来设置双击超时值。 它还可以通过将SPI_SETDOUBLECLKWIDTH 和 SPI_SETDOUBLECLKHEIGHT 标志传递给 SystemParametersInfo 来设置系统用来检测双击的矩形的大小。 但是请注意,设置双击超时值和矩形会影响所有应用程序。
默认情况下,应用程序定义的窗口不会接收双击消息。 由于生成双击消息所涉及的系统开销,因此这些消息仅针对属于具有 CS_DBLCLKS 类样式的类的窗口生成。 应用程序在注册窗口类时必须设置此样式。 有关详细信息,请参阅窗口类。
双击消息始终是四条消息系列中的第三条消息。 前两条消息是第一次单击生成的按钮按下和按钮松开消息。 第二次单击会生成双击消息,然后是另一条按钮松开消息。 例如,双击鼠标左键会生成以下消息序列:
因为窗口在收到双击消息之前总是会收到一条按钮按下的消息,所以应用程序通常会使用双击消息来扩展它在按钮按下消息期间开始的任务。 例如,当用户在 Microsoft 画图的调色板中单击颜色时,画图会在调色板旁边显示选定的颜色。 当用户双击一种颜色时,画图将显示颜色并打开编辑颜色对话框。
非工作区鼠标消息
当鼠标事件发生在除工作区之外的窗口的任何部分时,窗口会收到非工作区鼠标消息。 窗口的非工作区由其边框、菜单栏、标题栏、滚动条、窗口菜单、最小化按钮和最大化按钮组成。
系统生成非工作区消息主要供其自身使用。 例如,当光标热点移动到窗口边界时,系统使用非工作区消息将光标更改为双向箭头。 窗口必须将非工作区鼠标消息传递给 DefWindowProc 函数,才能利用内置鼠标界面。
每个工作区鼠标消息都有一个相应的非工作区鼠标消息。 这些消息的名称相似,不同之处在于非工作区的命名常量包括字母 NC。 例如,在非工作区中移动光标会生成一条 WM_NCMOUSEMOVE 消息,在光标位于非工作区时按下鼠标左键会生成一条 WM_NCLBUTTONDOWN 消息。
非工作区鼠标消息的 lParam 参数是包含光标热点的 x 坐标和 y 坐标的结构。 与工作区鼠标消息的坐标不同,坐标是在屏幕坐标而不是工作区坐标中指定的。 在屏幕坐标系中,屏幕上的所有点都相对于屏幕左上角的坐标 (0,0)。
wParam 参数包含一个命中测试值,该值指示鼠标事件发生在非工作区的何处。 下节介绍了命中测试值的目的。
WM_NCHITTEST 消息
每当发生鼠标事件时,系统都会向包含光标热点的窗口或捕获鼠标的窗口发送一条 WM_NCHITTEST 消息。 系统使用此消息来确定是发送工作区还是非工作区鼠标消息。 必须接收鼠标移动和鼠标按钮消息的应用程序必须将 WM_NCHITTEST 消息传递给 DefWindowProc 函数。
WM_NCHITTEST 消息的 lParam 参数包含光标热点的屏幕坐标。 DefWindowProc 函数检查坐标并返回一个命中测试值,该值指示热点的位置。 命中测试值可以是以下值之一。
值 | 热点位置 |
---|---|
HTBORDER | 在没有大小调整边框的窗口边框中。 |
HTBOTTOM | 在窗口的下水平边框中。 |
HTBOTTOMLEFT | 在窗口边框的左下角。 |
HTBOTTOMRIGHT | 在窗口边框的右下角。 |
HTCAPTION | 在标题栏中。 |
HTCLIENT | 在工作区中。 |
HTCLOSE | 在“关闭”按钮中。 |
HTERROR | 在屏幕背景上或窗口之间的分割线上(与 HTNOWHERE 相同,只是 DefWindowProc 函数会生成系统蜂鸣音以指示错误)。 |
HTGROWBOX | 在大小框中(与 HTSIZE 相同)。 |
HTHELP | 在“帮助”按钮中。 |
HTHSCROLL | 在水平滚动条中。 |
HTLEFT | 在窗口的左边框。 |
HTMENU | 在菜单中。 |
HTMAXBUTTON | 在“最大化”按钮中。 |
HTMINBUTTON | 在“最小化”按钮中。 |
HTNOWHERE | 在屏幕背景上,或在窗口之间的分隔线上。 |
HTREDUCE | 在“最小化”按钮中。 |
HTRIGHT | 在窗口的右边框。 |
HTSIZE | 在大小框中(与 HTGROWBOX 相同)。 |
HTSYSMENU | 在 系统菜单或子窗口中的关闭按钮中。 |
HTTOP | 在窗口的上水平边框中。 |
HTTOPLEFT | 在窗口边框的左上角。 |
HTTOPRIGHT | 在窗口边框的右上角。 |
HTTRANSPARENT | 在当前由同一线程中的另一个窗口覆盖的窗口中。 |
HTVSCROLL | 在垂直滚动条中。 |
HTZOOM | 在最大化按钮中。 |
如果光标位于窗口的工作区中,则 DefWindowProc 会将 HTCLIENT 命中测试值返回给窗口过程。 当窗口过程将此代码返回到系统时,系统将光标热点的屏幕坐标转换为工作区坐标,然后发布相应的工作区鼠标消息。
当光标热点位于窗口的非工作区时,DefWindowProc 函数返回其他命中测试值之一。 当窗口过程返回这些命中测试值之一时,系统会发布一条非工作区鼠标消息,将命中测试值放置在消息的 wParam 参数中,将光标坐标放置在 lParam 参数中。
鼠标 Sonar
当用户按下并释放 Ctrl 键时,鼠标 Sonar 辅助功能会在指针周围简要显示几个同心圆。 此功能可帮助用户在杂乱的屏幕上或分辨率设置为高的屏幕上、质量较差的显示器上或视力受损的用户上定位鼠标指针。 有关详细信息,请参阅 SystemParametersInfo 中的以下标志:
SPI_GETMOUSESONAR
SPI_SETMOUSESONAR
鼠标消失
鼠标消失辅助功能在用户键入时隐藏指针。 当用户移动鼠标时,鼠标指针会重新出现。 此功能可防止指针遮挡正在键入的文本,例如在电子邮件或其他文档中。 有关详细信息,请参阅 SystemParametersInfo 中的以下标志:
SPI_GETMOUSEVANISH
SPI_SETMOUSEVANISH
鼠标滚轮
鼠标滚轮结合了滚轮和鼠标按钮的功能。 滚轮上有离散的、均匀分布的凹口。 当你旋转滚轮时,当遇到每个凹口时,滚轮消息都会发送到应用程序。 滚轮按钮也可以作为普通的 Windows 中间(第三个)按钮操作。 按下并释放鼠标滚轮会发送标准的 WM_MBUTTONUP 和 WM_MBUTTONDOWN 消息。 双击第三个按钮会发送标准的 WM_MBUTTONDBLCLK 消息。
鼠标滚轮是通过 WM_MOUSEWHEEL 消息来支持的。
旋转鼠标会将 WM_MOUSEWHEEL 消息发送到焦点窗口。 DefWindowProc 函数将消息传播到窗口的父级。 不应在内部转发消息,因为 DefWindowProc 将它向上传播到父链,直到找到处理它的窗口。
确定滚动行数
应用程序使用 SystemParametersInfo 函数来检索文档在每次滚动操作(滚轮凹口)时滚动的行数。 为检索行数,应用程序会进行以下调用:
SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, pulScrollLines, 0)
变量“pulScrollLines”指向一个无符号整数值,当鼠标滚轮在没有修饰键的情况下旋转时,该整数值接收建议的滚动行数:
- 如果此数字为 0,则不发生滚动。
- 如果此数字为 WHEEL_PAGESCROLL,则滚轮滚动应被解释为在滚动条的向下或向上翻页区域单击一次。
- 如果要滚动的行数大于可查看的行数,则滚动操作也应被解释为向下翻页或向上翻页操作。
滚动行数的默认值为 3。 如果用户通过使用“控制面板”中的“鼠标属性”表更改滚动行数,操作系统会向指定了 SPI_SETWHEELSCROLLLINES 的所有顶级窗口广播 WM_SETTINGCHANGE 消息。 当应用程序收到 WM_SETTINGCHANGE 消息时,它可以通过调用以下命令来获取新的滚动行数:
SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, pulScrollLines, 0)
滚动的控件
下表列出了具有滚动功能的控件(包括用户设置的滚动行)。
控制 | 滚动 |
---|---|
编辑控件 | 水平和垂直。 |
列表框控件 | 水平和垂直。 |
组合框 | 如果不下拉,则每次滚动都会检索下一个项目或上一个项目。 下拉后,每个滚动将消息转发到列表框,列表框相应地滚动。 |
CMD(命令行) | 垂直。 |
树视图 | 水平和垂直。 |
列表视图 | 水平和垂直。 |
向上/向下滚动 | 一次一个项目。 |
跟踪条滚动 | 一次一个项目。 |
Microsoft Rich Edit 1.0 | 垂直。 请注意,Exchange 客户端具有其自己的列表视图和树视图控件版本,这些控件不支持滚轮。 |
Microsoft Rich Edit 2.0 | 垂直。 |
使用滚轮检测鼠标
若要确定是否连接了带滚轮的鼠标,请使用 SM_MOUSEWHEELPRESENT 调用 GetSystemMetrics。 返回值 TRUE 表示鼠标已连接。
以下示例来自多行编辑控件的窗口过程:
BOOL ScrollLines(
PWNDDATA pwndData, //scrolls the window indicated
int cLinesToScroll); //number of times
short gcWheelDelta; //wheel delta from roll
PWNDDATA pWndData; //pointer to structure containing info about the window
UINT gucWheelScrollLines=0;//number of lines to scroll on a wheel rotation
gucWheelScrollLines = SystemParametersInfo(SPI_GETWHEELSCROLLLINES,
0,
pulScrollLines,
0);
case WM_MOUSEWHEEL:
/*
* Do not handle zoom and datazoom.
*/
if (wParam & (MK_SHIFT | MK_CONTROL)) {
goto PassToDefaultWindowProc;
}
gcWheelDelta -= (short) HIWORD(wParam);
if (abs(gcWheelDelta) >= WHEEL_DELTA && gucWheelScrollLines > 0)
{
int cLineScroll;
/*
* Limit a roll of one (1) WHEEL_DELTA to
* scroll one (1) page.
*/
cLineScroll = (int) min(
(UINT) pWndData->ichLinesOnScreen - 1,
gucWheelScrollLines);
if (cLineScroll == 0) {
cLineScroll++;
}
cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
assert(cLineScroll != 0);
gcWheelDelta = gcWheelDelta % WHEEL_DELTA;
return ScrollLines(pWndData, cLineScroll);
}
break;
窗口激活
当用户单击非活动顶级窗口或非活动顶级窗口的子窗口时,系统会向该顶级窗口或子窗口 WM_MOUSEACTIVATE 发送消息(以及其他消息)。 系统在向窗口发布 WM_NCHITTEST 消息后,但在发布按钮按下消息之前发送此消息。 当 WM_MOUSEACTIVATE 传递给 DefWindowProc 函数时,系统将激活顶级窗口,然后将按钮按下消息发布到顶级窗口或子窗口。
通过处理 WM_MOUSEACTIVATE,窗口可以控制顶级窗口是否会因鼠标点击而成为活动窗口,以及被点击的窗口是否会收到后续的按钮按下消息。 它通过在处理 WM_MOUSEACTIVATE 后返回以下值之一来实现。
值 | 含义 |
---|---|
MA_ACTIVATE | 激活窗口,并且不丢弃鼠标消息。 |
MA_NOACTIVATE | 不激活窗口,并且不丢弃鼠标消息。 |
MA_ACTIVATEANDEAT | 激活窗口,并丢弃鼠标消息。 |
MA_NOACTIVATEANDEAT | 不激活窗口,但丢弃鼠标消息。 |