前面的部分讨论了鼠标单击和鼠标移动。 下面是一些可以使用鼠标执行的其他操作。
拖动 UI 元素
如果 UI 支持拖动 UI 元素,则应在鼠标按下消息处理程序中调用另一个函数:
DragDetect
。 如果用户启动应解释为拖动的鼠标手势,
DragDetect
函数将返回
TRUE
。 以下代码演示如何使用此函数。
case WM_LBUTTONDOWN:
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
if (DragDetect(m_hwnd, pt))
// Start dragging.
return 0;
想法如下:当程序支持拖放时,你不希望每次单击鼠标都解释为拖动。 否则,用户可能会意外拖动某些内容,例如,当用户只是想单击它 (以) 选择它。 但是,如果鼠标特别敏感,则很难在单击时保持鼠标完全静止。 因此,Windows 定义了几个像素的拖动阈值。 当用户按下鼠标按钮时,除非鼠标超过此阈值,否则不会将其视为拖动。 DragDetect 函数测试是否已达到此阈值。 如果函数返回 TRUE,则可以将鼠标单击解释为拖动。 否则,请勿这样做。
如果 DragDetect 返回 FALSE,则当用户释放鼠标按钮时,Windows 将禁止显示 WM_LBUTTONUP 消息。 因此,除非程序当前处于支持拖动的模式,否则不要调用 DragDetect 。 (例如,如果已选择可拖动的 UI 元素。) 在本模块结束时,我们将看到一个使用 DragDetect 函数的较长代码示例。
有时,你可能希望将光标限制在工作区或工作区的一部分。 ClipCursor 函数将光标的移动限制为指定的矩形。 此矩形以屏幕坐标而不是客户端坐标提供,因此点 (0, 0) 表示屏幕的左上角。 若要将客户端坐标转换为屏幕坐标,请调用函数 ClientToScreen。
以下代码将光标限制在窗口的工作区。
// Get the window client area.
RECT rc;
GetClientRect(m_hwnd, &rc);
// Convert the client area to screen coordinates.
POINT pt = { rc.left, rc.top };
POINT pt2 = { rc.right, rc.bottom };
ClientToScreen(m_hwnd, &pt);
ClientToScreen(m_hwnd, &pt2);
SetRect(&rc, pt.x, pt.y, pt2.x, pt2.y);
// Confine the cursor.
ClipCursor(&rc);
ClipCursor 采用 RECT 结构,但 ClientToScreen 采用 POINT 结构。 矩形由其左上角和右下角定义。 可以将光标限制在任何矩形区域(包括窗口外部的区域),但将光标限制在工作区是使用 函数的典型方法。 将光标完全限制在窗口外部的区域是不寻常的,用户可能会将其视为 bug。
若要删除此限制,请使用 NULL 值调用 ClipCursor。
ClipCursor(NULL);
鼠标跟踪事件:悬停和离开
默认情况下,其他两条鼠标消息处于禁用状态,但对某些应用程序可能很有用:
WM_MOUSEHOVER:光标已悬停在工作区上固定一段时间。
WM_MOUSELEAVE:光标已离开工作区。
若要启用这些消息,请调用 TrackMouseEvent 函数。
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(tme);
tme.hwndTrack = hwnd;
tme.dwFlags = TME_HOVER | TME_LEAVE;
tme.dwHoverTime = HOVER_DEFAULT;
TrackMouseEvent(&tme);
TRACKMOUSEEVENT 结构包含函数的参数。 结构的 dwFlags 成员包含位标志,这些标志指定你感兴趣的跟踪消息。 可以选择获取 WM_MOUSEHOVER 和 WM_MOUSELEAVE,如下所示,也可以只获取两者中的一个。 dwHoverTime 成员指定在系统生成悬停消息之前鼠标需要悬停的时间。 此值以毫秒为单位。 常 量HOVER_DEFAULT 表示使用系统默认值。
收到请求的消息之一后, TrackMouseEvent 函数将重置。 必须再次调用它才能获取另一条跟踪消息。 但是,在再次调用 TrackMouseEvent 之前,应等到下一个鼠标移动消息。 否则,你的窗口可能会充斥着跟踪消息。 例如,如果鼠标悬停,系统将继续在鼠标静止时生成 WM_MOUSEHOVER 消息流。 在鼠标移动到另一个位置并再次悬停之前,你实际上不需要另一个 WM_MOUSEHOVER 消息。
下面是一个可用于管理鼠标跟踪事件的小型帮助程序类。
class MouseTrackEvents
bool m_bMouseTracking;
public:
MouseTrackEvents() : m_bMouseTracking(false)
void OnMouseMove(HWND hwnd)
if (!m_bMouseTracking)
// Enable mouse tracking.
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(tme);
tme.hwndTrack = hwnd;
tme.dwFlags = TME_HOVER | TME_LEAVE;
tme.dwHoverTime = HOVER_DEFAULT;
TrackMouseEvent(&tme);
m_bMouseTracking = true;
void Reset(HWND hwnd)
m_bMouseTracking = false;
下一个示例演示如何在窗口过程中使用此类。
LRESULT MainWindow::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
switch (uMsg)
case WM_MOUSEMOVE:
mouseTrack.OnMouseMove(m_hwnd); // Start tracking.
// TODO: Handle the mouse-move message.
return 0;
case WM_MOUSELEAVE:
// TODO: Handle the mouse-leave message.
mouseTrack.Reset(m_hwnd);
return 0;
case WM_MOUSEHOVER:
// TODO: Handle the mouse-hover message.
mouseTrack.Reset(m_hwnd);
return 0;
return DefWindowProc(m_hwnd, uMsg, wParam, lParam);
鼠标跟踪事件需要系统进行其他处理,因此,如果不需要它们,请将其禁用。
为了完整性,下面是一个函数,用于查询系统的默认悬停超时。
UINT GetMouseHoverTime()
UINT msec;
if (SystemParametersInfo(SPI_GETMOUSEHOVERTIME, 0, &msec, 0))
return msec;
return 0;
以下函数检查是否存在鼠标滚轮。
BOOL IsMouseWheelPresent()
return (GetSystemMetrics(SM_MOUSEWHEELPRESENT) != 0);
如果用户旋转鼠标滚轮,具有焦点的窗口将收到 WM_MOUSEWHEEL 消息。 此消息的 wParam 参数包含一个名为 delta 的整数值,用于测量滚轮旋转的距离。 增量使用任意单位,其中 120 个单位定义为执行一个“操作”所需的旋转。当然,操作的定义取决于程序。 例如,如果使用鼠标滚轮滚动文本,则每 120 个旋转单位将滚动一行文本。
增量的符号指示旋转方向:
正:向前旋转,远离用户。
负数:向后旋转,朝用户旋转。
增量的值连同一些其他标志一起放置在 wParam 中。 使用 GET_WHEEL_DELTA_WPARAM 宏获取增量的值。
int delta = GET_WHEEL_DELTA_WPARAM(wParam);
如果鼠标滚轮具有高分辨率,则增量的绝对值可能小于 120。 在这种情况下,如果操作以较小的增量发生有意义,则可以执行此操作。 例如,文本可以按小于一行的增量滚动。 否则,累积总增量,直到滚轮旋转到足以执行操作为止。 将未使用的增量存储在变量中,当 120 个单位累积 (正或负) 时,执行操作。