MFC的OnMouseMove移動位置和OnMouseWheel縮放實現
1. 基本作用
OnMouseMove響應滑鼠移動時間
OnMouseWheel響應滑鼠中鍵的滾動
2. 引數說明
afx_msgvoidOnMouseMove(UINTnFlags, CPointpoint);nFlags說明:指示虛擬按鍵是否按下 ,此引數可以是任何下列值的組合
MK_CONTROL 當CTRL鍵按下時 MK_LBUTTON 當滑鼠左鍵按下時 MK_MBUTTON 當滑鼠中鍵按下時(滾動不屬於按下) MK_RBUTTON 當滑鼠右鍵按下時. MK_SHIFT 當SHIFT按下時。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()來釋放裝置控制代碼。