实战经验:鼠标在控件上悬停和离开的使用

实战经验:鼠标在控件上悬停和离开的使用

作者:BlogUpdater |  时间:2018-05-13 |  浏览:2925 |  评论已关闭 条评论

问题

在最近的工作中,碰到这样一个需求:系统能根据鼠标是否在控件上来做出不同的响应。这里所说的是否在控件上,实际上有个专门的名称:Hover和Leave。所谓Hover,指的是鼠标在窗口所在的矩形区域上悬停指定的一段时间。所谓Leave,则是指鼠标离开窗口所在的矩形区域。

使用到的消息
在Windows中,分别使用WM_MOUSEHOVER和WM_MOUSELEAVE来表示这两种事件。我们先来看看MSDN对这两种消息的描述:

WM_MOUSEHOVER:
Posted to a window when the cursor hovers over the client area of the window for the period of time specified in a prior call to TrackMouseEvent.

WM_MOUSELEAVE:
Posted to a window when the cursor leaves the client area of the window specified in a prior call to TrackMouseEvent.

这里的描述中,提到一个重要的函数TrackMouseEvent。我们在来看看这个函数。
TrackMouseEvent:
Posts messages when the mouse pointer leaves a window or hovers over a window for a specified amount of time.

此函数的主要作用是当鼠标光标悬停在某个窗口上一段指定的时间,或者离开某个窗口时,发送WM_MOUSEHOVER或者WM_MOUSELEAVE消息出来。
所以,为了触发WM_MOUSEHOVER/WM_MOUSELEAVE消息,必须在合适的时间点调用TrackMouseEvent。那什么时候是合适的时间点呢?

鼠标会根据用户的操控随时移动,所以,最佳调用TrackMouseEvent的时间点:WM_MOUSEMOVE事件处理函数中。

对WM_MOUSEMOVE的处理

我们先在WM_MOUSEMOVE事件处理函数中添加对TrackMouseEvent的调用,这里我们创建了一个自定义类CMyButton,该类继承自CMFCButton:


BEGIN_MESSAGE_MAP(CMyButton, CMFCButton)
	ON_WM_MOUSEMOVE()
END_MESSAGE_MAP()

void CMyButton::OnMouseMove(UINT nFlags, CPoint point)
{
	if (m_bTrackingMouse)
	{
		TRACKMOUSEEVENT tme;
		tme.cbSize = sizeof(TRACKMOUSEEVENT);
		tme.dwFlags = TME_LEAVE | TME_HOVER;
		tme.hwndTrack = GetSafeHwnd();
		tme.dwHoverTime = 1000;
		if (::_TrackMouseEvent(&tme))
		{
			m_bTrackingMouse = false;
		}
	}
	
	CMFCButton::OnMouseMove(nFlags, point);
}

代码解释:
1) m_bTrackingMouse用来表示是否进行鼠标跟踪,这个变量后面会使用到。
2) dwFlags指定感兴趣的事件消息,这里设定为TME_LEAVE | TME_HOVER,表示应用希望收到WM_MOUSEHOVER和WM_MOUSELEAVE消息。
3) hwndTrack:指定鼠标悬停或离开的目标窗口,只会在这个窗口上做出的动作才会被系统检测到。
4) dwHoverTime:指定一段时间,单位:毫秒。当鼠标悬停在指定窗口上,经历了这段时间之后,系统才会触发WM_MOUSEHOVER消息。
5) 注意,这里需要添加ON_WM_MOUSEMOVE()以建立消息映射。
6) 这里使用到了_TrackMouseEvent,而不是TrackMouseEvent的原因如下:
The _TrackMouseEvent function calls TrackMouseEvent if it exists, otherwise _TrackMouseEvent emulates TrackMouseEvent.

接收悬停或离开消息

接下来,我们分别对WM_MOUSEHOVER和WM_MOUSELEAVE消息进行处理:

BEGIN_MESSAGE_MAP(CMyButton, CMFCButton)
	ON_WM_MOUSEMOVE()
	ON_WM_MOUSEHOVER()
	ON_WM_MOUSELEAVE()
END_MESSAGE_MAP()

/************************************************************************
* WM_MOUSEHOVER消息处理
************************************************************************/
void CMyButton::OnMouseHover(UINT nFlags, CPoint point)
{
	m_bTrackingMouse = false;

	AfxMessageBox(_T("检测到鼠标悬停"));
}

/************************************************************************
* WM_MOUSELEAVE消息处理
************************************************************************/
void CMyButton::OnMouseLeave()
{
	AfxMessageBox(_T("检测到鼠标离开"));
	m_bTrackingMouse = true;
}

代码解释:
1) 分别使用ON_WM_MOUSEHOVER和ON_WM_MOUSELEAVE这两个宏建立消息映射。
2) 当收到鼠标悬停消息时,可以知道鼠标已经位于窗口所在矩形中了,不再需要鼠标跟踪了。这个时候设置m_bTrackingMouse为false,就会禁用后续TrackMouseEvent的调用,防止系统不必要的鼠标跟踪。
3) 当收到鼠标离开消息时,表示此时鼠标已经离开窗口所在矩形,设置m_bTrackingMouse为true,以重新启用鼠标跟踪。

注意事项
1) 有些控件,比如CStatic控件,默认是不会触发WM_MOUSEHOVER和WM_MOUSELEAVE消息的,需要设置Notify为true。

总结
本文通过实际的代码,演示了鼠标悬停和离开的消息的产生以及触发流程。通过这次实践,我们可以在控件自绘中加入此功能代码,实现诸如鼠标悬停时改变控件外观等特效。

标签:

评论已关闭。