前面的部分讨论了鼠标单击和鼠标移动。 下面是一些可以使用鼠标执行的其他操作。

拖动 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_MOUSEHOVERWM_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 个单位累积 (正或负) 时,执行操作。