捕获鼠标






4.89/5 (14投票s)
2000 年 3 月 4 日

163969
关于 TrackMouseEvent 和 Win32 SetCapture 的注意事项
引言
应用程序的 UI 实现有时需要捕获鼠标。以下情况需要考虑:
- 您需要可靠的鼠标悬停检测。
- 您正在实现某种拖放界面。
- 您想知道鼠标悬停在哪个窗口上。
关于 SetCapture API 的注意事项
SetCapture()
API 的行为有点复杂,并且在 Platform SDK 中文档记录不佳。如果您了解使用鼠标捕获的以下限制,就能更好地理解如何在应用程序中使用 SetCapture()
:
一次只能有一个窗口拥有鼠标捕获。窗口可以通过调用 SetCapture()
API 来请求鼠标捕获,该窗口将一直拥有鼠标捕获,直到调用 ReleaseCapture()
API 或调用 SetCapture()
将鼠标捕获指向另一个窗口。
此外,还有两种类型的捕获,我称之为前台捕获和后台捕获。当满足以下两个条件时,即可获得前台捕获:
- 当前线程是前台线程(即,它拥有前台窗口)。
- 至少有一个鼠标按钮被按下。
否则(如果当前线程不是前台线程,或者没有鼠标按钮被按下),该窗口仅获得后台捕获。
如果任何时候所有鼠标按钮都被释放,鼠标捕获将自动恢复到后台。
前台捕获和后台捕获的区别如下:
前台捕获
具有前台捕获的窗口会接收系统中所有窗口的所有鼠标消息。
后台捕获
一旦捕获恢复到后台捕获,窗口只会接收以下鼠标消息:
- 同一线程拥有的所有窗口。
- 所有线程上的所有窗口,前提是这些窗口与具有捕获的窗口共享同一个顶级窗口。
使用 SetCapture 实现拖动操作
要在应用程序中实现拖动操作,您需要实现以下消息处理程序:
WM_LBUTTONDOWN WM_RBUTTONDOWN | 拖动操作通常在用户单击某个对象并开始移动鼠标时开始。如果用户单击了可拖动的对象,请使用 DragDetect() API 来检测拖动操作是否已开始。一旦确认拖动操作开始,请调用 SetCapture() 。请注意,各种支持拖动的控件会检测到拖动何时开始,并将消息(例如 LVN_BEGINDRAG )发送给其父窗口。 |
WM_LBUTTONUP WM_RBUTTONUP | 如果鼠标释放完成了拖动(有关鼠标释放可能未完成拖动的原因,请参阅下面的备注),则必须调用 ReleaseCapture() 以允许其他窗口访问鼠标消息。 |
WM_CHAR | 通过按 ESC 键通常可以取消拖动操作。如果需要,请取消拖动操作并调用 ReleaseCapture() 。 |
WM_CANCELMODE | 当窗口管理器检测到需要应用程序取消任何已进入的模态状态的更改时,它会发送此消息。请取消拖动操作并调用 ReleaseCapture() 。 |
WM_CAPTURECHANGED | 捕获已被清除,或者其他窗口已获取它。继续拖动操作可能没有意义,因此应将其取消。由于您已显式失去捕获,因此无需调用 ReleaseCapture() 。 |
WM_SETCURSOR | 鼠标捕获会中断正常的鼠标处理流程。WM_SETCURSOR 消息不会分派给已获得鼠标捕获的窗口。如果应在拖动操作开始时通过 SetCursor 设置光标以指示拖动,并且如果光标需要更改以向用户提供反馈,则应在响应 WM_MOUSEMOVE 时进行设置。 |
备注
除了鼠标按下或初始拖动操作开始检测外,大多数应用程序都在模态循环中实现其拖动代码,以防止主应用程序窗口过程变得混乱。
另请注意,大多数系统拖动操作允许用户通过按住另一按钮,然后释放初始按钮来“交换”按钮。如果拖动操作不在模态循环中实现,则必须特别处理这种情况,以防止启动另一个拖动操作。
使用 TrackMouseEvent
此 API 有两个版本可用:
TrackMouseEvent()
:在 Windows 98 及更高版本和 Windows NT 4 及更高版本上可用作标准的窗口管理器函数。_TrackMouseEvent()
:在所有安装了 Internet Explorer 3 及更高版本的系统上,可在公共控件库中使用。
如果可以忽略 Windows 95 作为目标平台,请使用 TrackMouseEvent()
。如果您需要以 Windows 95 为目标平台,并且可以假定计算机至少安装了 IE3,则使用 TrackMouseEvent()
。如果您需要在 Windows 95 上使用 TrackMouseEvent()
功能,并且无法假定至少安装了 IE3,那么以下快速解决方法将演示基本功能。
自己实现 TrackMouseEvent
TrackMouseEvent()
的完整自定义实现必须实现消息挂钩,以便它可以挂接目标为任何窗口的消息。任何需要检测鼠标进入、离开或悬停事件的窗口都可以使用此类代码。
#define TID_POLLMOUSE 100
#define MOUSE_POLL_DELAY 500
case WM_MOUSEMOVE:
SetTimer(hwnd,TID_POLLMOUSE,MOUSE_POLL_DELAY,NULL);
break;
case WM_TIMER:
RECT rc;
POINT pt;
GetWindowRect(hwnd,&rc);
GetCursorPos(&pt);
if(PtInRect(&rc,pt))
{
PostMessage(hwnd,WM_MOUSEHOVER,0,0L);
break;
}
PostMessage(hwnd,WM_MOUSELEAVE,0,0L);
KillTimer(hwnd,TID_POLLMOUSE);
break;
通过使用 SetCapture()
可以更快地检测到鼠标离开,从而可以创建更具响应性的版本。
#define TID_POLLMOUSE 100
#define MOUSE_POLL_DELAY 500
case WM_MOUSEMOVE:
RECT rc;
POINT pt;
GetWindowRect(hwnd,&rc);
pt.x = GET_X_LPARAM(lParam);
pt.y = GET_Y_LPARAM(lParam);
if(PtInRect(&rc,pt))
{
SetTimer(hwnd,TID_POLLMOUSE,MOUSE_POLL_DELAY,NULL);
if(hwnd != GetCapture())
{
SetCapture(hwnd);
PostMessage(hwnd,WM_MOUSEENTER,0,0L);
}
break;
}
ReleaseCapture();
KillTimer(hwnd,TID_POLLMOUSE);
PostMessage(hwnd,WM_MOUSELEAVE,0,0L);
break;
case WM_TIMER:
GetWindowRect(hwnd,&rc);
GetCursorPos(&pt);
if(PtInRect(&rc,pt))
{
PostMessage(hwnd,WM_MOUSEHOVER,0,0L);
break;
}
ReleaseCapture();
KillTimer(hwnd,TID_POLLMOUSE);
PostMessage(hwnd,WM_MOUSELEAVE,0,0L);
break;
提供的代码示例在许多方面与 TrackMouseEvent()
API 不同:
- 在实际应用程序中,您需要为子窗口执行悬停检测。该示例假定您仅在主窗口上执行悬停检测。
TrackMouseEvent()
不会发送WM_MOUSEENTER
消息。也没有定义这样的消息。该代码仅演示了如何实现这样的消息。TrackMouseEvent
是一个一次性 API。收到通知后,您必须再次调用它才能接收后续通知。提供的示例代码将重复发送WM_MOUSEHOVER
事件 - 没有提供停止通知的机制,或者只在请求时发送通知。
请随时根据您的需求扩展代码。
请通过电子邮件将任何意见或错误报告发送给我。有关本文的任何更新,请在此处查看我的网站 此处。
知识库参考
有关此主题的更多信息,请参阅以下 KB 文章:
许可证
本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。
作者可能使用的许可证列表可以在此处找到。