[WinAPI] 使用 _TrackMouseEvent 来产生窗口的 鼠标停留(WM_MOUSEHOVER)与 鼠标离开(WM_MOUSELEAVE)消息

陪她去流浪 桃子 2016年11月10日 编辑 阅读次数:3518

自绘或子类化控件时,有时需要处理鼠标进入(MouseEnter)/鼠标离开(MouseLeave)/鼠标停留(MouseHover)消息,虽然有定义这两个消息常量(WM_MOUSEHOVER 与 WM_MOUSELEAVE),但默认情况下 Windows 的窗口消息是不会产生这三个消息的。但提供了一个 API 函数 _TrackMouseEvent 可以使用,这个函数可以使 Windows 产生 鼠标停留(WM_MOUSEHOVER)与 鼠标离开(WM_MOUSELEAVE)消息,至于鼠标进入消息(Windows 没有定义 WM_MOUSEENTER 宏),直接手动在第一次鼠标移动的时候模拟产生就行了。

不过不爽的是 鼠标离开消息 Windows 没有传递离开时的坐标过来,所以这个也得手动模拟:每次鼠标移动的时候记录下坐标,在收到鼠标离开消息时,采用此值即可。下面的代码没有作此处理,如有需要可手动加上。

效果图:

下面简短的示例代码测试并产生了这些消息。虽然是用 ATL 写的,但功能实现本身并不依赖 ATL,所以可以直接在纯 SDK 开发中使用。代码本身也非常简单,就无需多作介绍了。

下载代码:source.cpp

#include <cstdio>

#include <atlbase.h>
#include <atlwin.h>

#pragma comment(lib, "comctl32.lib")

class TrackMouseEventWindow : public CWindowImpl<TrackMouseEventWindow>
{
protected:
    BEGIN_MSG_MAP(TrackMouseEventWindow)
        MESSAGE_HANDLER(WM_MOUSEMOVE,   OnMouseMove)
        MESSAGE_HANDLER(WM_MOUSEHOVER,  OnMouseHover)
        MESSAGE_HANDLER(WM_MOUSELEAVE,  OnMouseLeave)
        MESSAGE_HANDLER(WM_DESTROY,     OnDestroy)
    END_MSG_MAP()

public:
    TrackMouseEventWindow()
        : _bMouseTracking(false)
        , _bMouseWithin(false)
    {

    }

protected:
    LRESULT OnMouseMove(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        // 如果鼠标不在客户区内,就产生一个鼠标进入事件
        if(!_bMouseWithin) {
            OnMouseEnter(uMsg /* WM_MOUSEMOVE */, wParam, lParam, bHandled);
        }

        // 开始跟踪鼠标停留/离开事件
        if(!_bMouseTracking) {
            StartTrack();
            _bMouseTracking = true;
            return 0;
        }

        printf("鼠标移动: %d,%d\n", GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
        return 0;
    }
    LRESULT OnMouseHover(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        _bMouseTracking = false;
        printf("鼠标停留: %d,%d\n", GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
        return 0;
    }

    LRESULT OnMouseLeave(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        _bMouseTracking = false;
        _bMouseWithin = false;
        printf("鼠标离开: %d,%d\n", GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
        return 0;
    }

    LRESULT OnMouseEnter(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        _bMouseWithin = true;
        printf("鼠标进入: %d,%d\n", GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
        return 0;
    }

    LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        ::PostQuitMessage(0);
        return 0;
    }

protected:
    void StartTrack()
    {
        TRACKMOUSEEVENT tme = {0};
        tme.cbSize = sizeof(tme);
        tme.hwndTrack = m_hWnd;
        tme.dwFlags = TME_HOVER | TME_LEAVE;
        tme.dwHoverTime = HOVER_DEFAULT;
        _TrackMouseEvent(&tme);
    }

protected:
    bool _bMouseTracking;   // 是否正在跟踪鼠标
    bool _bMouseWithin;     // 鼠标是否在窗口内
};

int main()
{
    TrackMouseEventWindow wnd;
    wnd.Create(nullptr, nullptr, nullptr, WS_OVERLAPPEDWINDOW);
    wnd.ShowWindow(SW_SHOWNORMAL);

    MSG msg;
    while(::GetMessage(&msg, nullptr, 0, 0))
    {
        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
    }

    return (int)msg.wParam;
}

标签:WinAPI