鼠标输入概述
鼠标是应用程序的重要(但可选)用户输入设备。 一个编写良好的应用程序应该包括一个鼠标接口,但它不应仅依赖鼠标来获取用户输入。 该应用程序还应提供完整的键盘支持。
应用程序以发送或发布到其窗口的消息形式接收鼠标输入。
本部分涵盖了以下主题:
鼠标光标
当用户移动鼠标时,系统会在屏幕上移动一个称为鼠标光标的位图。 鼠标光标包含一个称为热点的单像素点,系统跟踪该点并将其识别为光标的位置。 当鼠标事件发生时,包含热点的窗口通常会接收到事件生成的鼠标消息。 窗口不需要处于活动状态或具有键盘焦点来接收鼠标消息。
系统维护一个控制鼠标速度的变量,即光标在用户移动鼠标时移动的距离。 可以使用带有 SPI_GETMOUSE 或 SPI_SETMOUSE 标志的 SystemParametersInfo 函数来检索或设置鼠标速度。 有关鼠标光标的详细信息,请参阅光标。
鼠标捕获
当鼠标事件发生时,系统通常会向包含光标热点的窗口发布鼠标消息。 应用程序可以通过使用 SetCapture 函数将鼠标消息路由到特定窗口来更改此行为。 该窗口会收到所有鼠标消息,直到应用程序调用 ReleaseCapture 函数或指定另一个捕获窗口,或者直到用户单击由另一个线程创建的窗口。
当鼠标捕获发生变化时,系统会向失去鼠标捕获的窗口发送 WM_CAPTURECHANGED 消息。 消息的 lParam 参数指定获取鼠标捕获的窗口的句柄。
只有前台窗口可以捕获鼠标输入。 当后台窗口尝试捕获鼠标输入时,它只接收当光标热点位于窗口可见部分内时发生的鼠标事件的消息。
如果窗口必须接收所有鼠标输入,即使光标移出窗口,捕获鼠标输入也很有用。 例如,应用程序通常在按下鼠标按钮事件后跟踪光标位置,直到鼠标按钮弹起事件发生。 如果应用程序未捕获鼠标输入,并且用户在窗口外松开鼠标按键,则窗口不会收到按钮弹起消息。
线程可以使用 GetCapture 函数来确定其某个窗口是否捕获了鼠标。 如果线程的某个窗口捕获了鼠标,GetCapture 将检索窗口的句柄。
鼠标单击锁定
“鼠标单击锁定”辅助功能使用户能够在单击后锁定主鼠标按钮。 对于应用程序,该按钮仍似乎是被按下的状态。 要解锁按钮,应用程序可以发送任何鼠标消息,或者用户可以单击任何鼠标按钮。 用户可通过此功能更轻松地进行复杂的鼠标组合。 例如,具有某些身体限制的用户可以更轻松地突出显示文本、拖动对象或打开菜单。 有关详细信息,请参阅以下标志和 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 函数是另一种反转按钮含义的方法。 但是请注意,鼠标是共享资源,因此反转按钮的含义会影响所有应用程序。
XBUTTON
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 函数获取和设置双击超时值。 或者,应用程序可以结合使用 SystemParametersInfo 函数和 SPI_SETDOUBLECLICKTIME 标志来设置双击超时值。 它还可以通过将 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 参数中。
鼠标声音波形
当用户按下和松开 CTRL 键时,鼠标声音波形辅助功能会在指针周围短暂显示几个同心圆。 此功能可帮助用户在杂乱或分辨率设置过高的屏幕上、在质量较差的显示器上或为视力受损的用户定位鼠标指针。 有关详细信息,请参阅 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。 如果用户通过使用“控制面板”中的“鼠标属性”表更改滚动行数,操作系统会将 WM_SETTINGCHANGE 消息广播到指定 SPI_SETWHEELSCROLLLINES 的所有顶级窗口。 当应用程序收到 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 | 不激活窗口,但丢弃鼠标消息。 |