前面的部分讨论了鼠标单击和鼠标移动。 下面是可以使用鼠标执行的一些其他作。
拖动 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 函数将重置。 必须再次调用它才能获取另一条跟踪消息。 但是,在再次调用 trackMouseE vent 之前,应等到下一个鼠标移动消息。 否则,窗口可能会充斥着跟踪消息。 例如,如果鼠标悬停,则当鼠标静止时,系统将继续生成 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;
}
else
{
return 0;
}
}
鼠标滚轮
以下函数检查鼠标滚轮是否存在。
BOOL IsMouseWheelPresent()
{
return (GetSystemMetrics(SM_MOUSEWHEELPRESENT) != 0);
}
如果用户旋转鼠标滚轮,具有焦点的窗口将收到 WM_MOUSEWHEEL 消息。 此消息的 wParam 参数包含一个名为 增量 的整数值,用于测量轮的旋转距离。 增量使用任意单位,其中 120 个单位定义为执行一个“作”所需的旋转。当然,作的定义取决于你的程序。 例如,如果鼠标滚轮用于滚动文本,则每 120 个旋转单位将滚动一行文本。
增量的符号指示旋转方向:
- 正:向前旋转,远离用户。
- 负数:向后旋转,向用户旋转。
增量值放置在 wParam 以及一些其他标志中。 使用 GET_WHEEL_DELTA_WPARAM 宏获取增量的值。
int delta = GET_WHEEL_DELTA_WPARAM(wParam);
如果鼠标滚轮具有高分辨率,增量的绝对值可能小于 120。 在这种情况下,如果作的增量较小,则可以执行此作。 例如,文本可以按小于一行的增量滚动。 否则,请累积总增量,直到滚轮旋转到足以执行作为止。 将未使用的增量存储在变量中,当 120 个单位累积(正或负数)时,请执行该作。
下一个