[WinAPI] 使用 _TrackMouseEvent 来产生窗口的 鼠标停留(WM_MOUSEHOVER)与 鼠标离开(WM_MOUSELEAVE)消息
自绘或子类化控件时,有时需要处理鼠标进入(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;
}