MFC的OnMouseMove移动位置和OnMouseWheel缩放实现
1. 基本作用
OnMouseMove响应鼠标移动时间
OnMouseWheel响应鼠标中键的滚动
2. 参数说明
nFlags说明:指示虚拟按键是否按下 ,此参数可以是任何下列值的组合
point说明:鼠标的X,Y坐标:该坐标为鼠标相对所在窗口左上角为基点的位置,是一个相对位置而不是在屏幕像素上的绝对位置。
afx_msg BOOL OnMouseWheel( UINT nFlags, shortzDelta, CPointpt );
nFlags同上
zDelta:大于0时为向上滚动,小于0时为向下滚动。A value less than zero indicates rotating back (toward the user) while a value greater than zero indicates rotating forward (away from the user). Windows下通常向上滚动缩小/窗口上移,反之放大/下移
pt::鼠标的X,Y坐标,是以其父窗口的左上角为基点的。Specifies the x- and y-coordinate of the cursor. These coordinates are always relative to the upper-left corner of the window.
3. 移动的效果实现
要实现移动,例如鼠标左键拖动butoon/图片在窗口上移动,实现的结果附加要求:鼠标放在button/图片的A点,移动之后,鼠标点依然在A点上
我们通过
a. 检测鼠标已在button/图片上(确保不是在哪都可以移动图标),并且左键按下
b. 记录鼠标当前点和上个点,计算两个的偏移值,然后使用这个偏移值来移动button/图片(MoveWindows)
(记录上个点的方法可以使用静态变量,移动完毕后,把当前点赋值给静态变量)(具体实现可以灵活处理)
实现原理是:相对静止---鼠标和对象相对位置不变,鼠标的偏移量,就是我们对象的偏移量
4. 缩放的效果实现(以鼠标点为中心缩放)
要实现缩放,例如中件滑轮向上滑动缩小,向下滑动放大button/图片,实现附加要求:鼠标放在button/图片的A点,缩放之后,鼠标点依然在A点上,缩放是以鼠标点为中心
a. 同样检测鼠标已在button/图片上(确保不是在哪都可以缩放button/图片)
b. 获取当前button/图片的高和宽(使用getClientRect)
c. 获取当前pt点x,y相对于button/图片位置,然后计算该位置相对于宽和高的比值
d. 判断zDelta正负确定放大缩小(按比例调整图片高度和宽度),并调整图片左上点(left,top)的位置,确保c中的比值不变(---确保了以鼠标所在点为中心放大或缩小)
实现原理是:相对移动---鼠标和所在对象点位置不变,鼠标所在对象点的周围 长和宽 成比例的缩放
在日常用到的很多软件里,当鼠标停留在某个菜单或者按钮上一定事件,会弹出关于这个空间的一些提示信息,提示信息可以利用MFC中的CToolTipCtrl 类来实现,输出提示并不难,但是如何判断鼠标停留在某个按钮上呢?可以在对话框中响应WM_SETCOUSE消息,在响应事件中判断发生事件的窗口句柄是不是该控件句柄,如果是做出响应的操作即可,但是如果要判断光标离开控件,则相对复杂一点。因为光标离开控件后,接收鼠标移动消息的窗口句柄肯定不是该控件句柄了,也可以解决,如下:
BOOLCMy12Dlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
if(pWnd->GetDlgCtrlID() == IDC_BUTTON3)
{
pWnd->SetWindowTextW(_T("Hover"));
}
else if(pWnd->GetDlgCtrlID() != IDC_BUTTON3)
{
GetDlgItem(IDC_BUTTON3)->SetWindowTextW(_T("Leave"));
}
returnCDialogEx::OnSetCursor(pWnd, nHitTest, message);
}
(注:这样做,当鼠标快速的移动的时候,就有可能出现鼠标已经离开了按钮,但按钮还是显示鼠标停留在上面的现象。)
但是如果这样的控件不止一个的时候,难道要用n个else if去判断该句柄是不是对应的控件句柄吗?这样做就有点不合理了,有没有更好的解决办法呢?
经过试验,以下方法可行且相对来说最好:
从Cbutton派生子类MyHoverBtn,在子类中响应WM_MOUSEMOVE事件,在该事件的响应函数中添加如下代码来响应鼠标的Hover和Leave事件:
void MyHoverBtn::OnMouseMove(UINT nFlags, CPointpoint)
{
TRACKMOUSEEVENT mouse_event;//定义鼠标移动事件结构体
mouse_event.cbSize = sizeof(mouse_event);//定义结构体大小
mouse_event.hwndTrack = m_hWnd;//关联窗口句柄
mouse_event.dwFlags = TME_HOVER |TME_LEAVE;//设置响应标记
mouse_event.dwHoverTime = 100/*HOVER_DEFAULT=400*/;//设置Hover的时间
_TrackMouseEvent(&mouse_event);
CButton::OnMouseMove(nFlags, point);
}
然后在子类MyHoverBtn中响应WM_MOUSEHOVER和WM_MOUSELEAVE消息,并在相应的事件响应函数中添加实现代码;例如
void MyHoverBtn::OnMouseLeave()
{
this->SetWindowTextW(_T("我离开了~"));
CButton::OnMouseLeave();
}
void MyHoverBtn::OnMouseHover(UINT nFlags, CPointpoint)
{
this->SetWindowTextW(_T("我来了~"));
CButton::OnMouseHover(nFlags, point);
}
网上有人说可以在对话框中重载PreTranslateMessage函数,判断消息类型和控件句柄,如果消息是WM_MOUSEMOVE,且该事件发生的控件句柄就是你要控制的控件句柄,则做出响应的处理,代码如下:
BOOLCMy12Dlg::PreTranslateMessage(MSG* pMsg)
{
CWnd* pWnd = GetDlgItem(IDC_BUTTON1);
if(pMsg->hwnd == pWnd->GetSafeHwnd() && pMsg->message ==WM_MOUSEMOVE)
{
CPoint point(pMsg->pt);
pWnd->ScreenToClient(&point);
CRect rect;
pWnd->GetClientRect(&rect);
if(rect.PtInRect(point))
pWnd->SetWindowTextW(_T("Hover"));
else
pWnd->SetWindowTextW(_T("Leave"));
returnTRUE;
}
returnCDialogEx::PreTranslateMessage(pMsg);
}
请注意绿色部分,这里不合理,因为当光标离开按钮控件后,鼠标移动的事件目标控件就不是该按钮了,与文章开头的方法是一样的,需要另外判断···
说了几种方式,还是派生子类,然后在子类中响应鼠标移动消息,从而做出对应操作最好。但是在设置Hover的时间的时候如果按照默认的HOVER_DEFAULT设置,也就是时间是400毫秒,那么鼠标移动到控件上面后需要等待这个时间才会响应OnMouseHover(),于是我将时间减小为100毫秒,比较合理了,如下:
mouse_event.dwHoverTime = 100/*HOVER_DEFAULT=400*/;//设置Hover的时间
就是不懂如果将时间设置到很小,会不会导致程序不断的提取鼠标移动消息,导致CPU占用增加呢?
经测试将时间设置为1毫秒,相对与设置为100毫秒,cpu也不会大幅增加,MSDN对该参数的解释如下:
dwHoverTime
Specifiesthe hover time-out (if TME_HOVER was specified in dwFlags), in milliseconds.Can be HOVER_DEFAULT, which means to use the system default hover time-out.
窗口重绘方面:
Invalidate()使窗口区域无效,以等待下一次重绘WM_PAINT消息将在消息队列为空时发送。与之类似的还有InvalidateRect(),可指定重绘具体的矩形区域。
UpdateWindow() 强制立即更新窗口,在OnPaint()函数中可以用GetUpdateRect()确定重绘区域或者判断是否需要重绘。
RedrawWindow与UpdateWindow类似,强制重绘窗口,但是它提供了更多的参数以设置重绘的区域等
在重绘时,需调用BeginPaint()来准备好用于绘画的windows设备环境,填充 PAINTSTRUCT结构,并将DC句柄返回用于重绘。
绘画结束时,需调用EndPaint()来释放设备句柄。