第五章已經講到,Windows只會把鍵盤消息發送到當前具有輸入焦點的窗口。鼠標消息則不同:當鼠標經過窗口或在窗口內被單擊,則即使該窗口是非活動窗口或不帶輸入焦點, 窗口過程還是會收到鼠標消息。Windows定義了 21種鼠標消息。不過,其中11種消息與 客戶區無關,稱為“非客戶區消息”。Windows應用程序經常忽略這類消息。
本節必須掌握的知識點:
??? ????客戶區鼠標消息
??? ????第35練:客戶區鼠標消息的處理
6.2.1 客戶區鼠標消息
■客戶區鼠標消息
當鼠標移經窗口客戶區時,窗口過程接收WM_MOUSEMOVE消息。在窗口客戶區內按下或釋放鼠標按鈕時,窗口過程接收如下表所示的消息:
按鈕 | 按下 | 釋放 | 第二次按下按鈕 |
左鍵 | WM_LBUTTONDOWN | WM_LBUTTONUP | WM_LBUTTONDBLCLK |
中鍵 | WM_MBUTTONDOWN | WM_MBUTTONUP | WM_MBUTTONDBLCLK |
右鍵 | WM_RBUTTONDOWN | WM_RBUTTONUP | WM_RBUTTONDBLCLK |
窗口過程只對三鍵鼠標接收MBUTTON消息,只對雙鍵鼠標接收RBUTTON消息。而只有當窗口類被定義成接收鼠標雙擊時,窗口過程才接收DBLCLK(雙擊)消息。
對所有這些消息來說,參數IParam包含了鼠標的位置信息,其中低位字表示x坐標, 高位字表示y坐標,它們都是相對于窗口客戶區左上角的相對坐標。利用LOWORD宏和 HIWORD宏,可以獲取這些坐標值:
X = LOWORD (IParam);
y = HIWORD (IParam);
參數wParam表示鼠標按鈕、Shift鍵和Ctrl鍵的狀態。可以利用WINUSER.H頭文件中定義的位掩碼來測試參數wParam。前綴MK代表“鼠標鍵”(mouse key)。
MK_LBUTTON?????? 按下左鍵
MK_MBUTTON????? 按下中鍵
MK_RBUTTON?????? 按下右鍵
MK_SHIFT????? ?????? 按下 Shift 鍵
MK_CONTROL????? 按下 Ctrl 鍵
例如,當接收到WM_LBUTTONDOWN消息時,若wparam & MK_SHIFT 的值為TRUE(非零),則表示按下鼠標左鍵的同時按下了 Shift鍵。
●處理Shift鍵
處理過程依賴Shift和Ctrl鍵的邏輯處理 | 單鍵鼠標模擬雙鍵鼠標 |
if (wParam & MK_SHIFT)? //按下Shift { ??? if (wParam & MK_CONTROL) ??? { ??????? [按下Shift + Ctrl鍵]; ??? ?} ??? else{ ??????? [只按下Shift鍵]; ??? } }else{????????????????? //未按Shift ??? if (wParam & MK_CONTROL) ??? { ??????? [只按下Ctrl鍵]; ??? }else{ ??????? [Shift和Ctrl都沒被按下]; ??? } } | case WM_LBUTTONDOWN: //未按Shift時,直接處理左鍵 ??? if (!(wParam & MK_SHIFT)) ??? { ??????? [這里處理左鍵]; ??????? return 0; ??? ?}?? //注意,這里沒有return。 ????//用戶按下了鼠標左鍵+Shift,執行以下代碼,模擬右鍵。 case WM_RBUTTONDOWN:? ??? [這里處理右鍵]; ??? return 0; 【注意】雙鍵鼠標也是可以正常處理的。單鍵鼠標可以通過按住鼠標左鍵+Shift,來模擬鼠標右鍵的功能。 |
【注意】GetKeyState可以通過VK_LBUTTON、VK_RBUTTON、VK_SHIFT、VK_CONTROL等獲取鼠標當前狀態。但鼠標或鍵盤未被按下的鍵不能使用GetKeyState。只有被按下時才會報告其按下狀態。(while(GetKeyState(VK_LBUTTON)>=0))是錯誤的代碼。
●鼠標移經窗口的客戶區時,Windows系統不會為鼠標經過的每個像素位置都產生 WM_MOUSEMOVE消息。程序收到的WM_MOUSEMOVE消息個數取決于鼠標硬件和窗口過程處理鼠標移動消息的速度。換言之,如果消息隊列里還有未處理的 WM_MOUSEMOVE消息,Windows就不會重復向消息隊列中添加該消息。試驗下面這個 CONNECT程序,可以對WM_MOUSEMOVE消息的產生速度有一個全面的了解。
●若在非活動窗口的客戶區內按下鼠標左鍵,Windows會將該窗口變為活動窗口,并向窗口過程發送WM_LBUTTONDOWN消息。當窗口過程接收到WM_LBUTTONDOWN消息時,程序就能夠安全地保證該窗口是活動窗口。但是,在事先沒有接收 WM_LBUTTONDOWN消息的情況下,窗口過程仍然可以接收WM_LBUTTONUP消息。 比如,當用戶在其他窗口內按下鼠標,再移動到用戶窗口,然后釋此時就會發生這種情況。類似地,當移動鼠標到另一個窗口再釋放時,前一個窗口過程在接收 WM_LBUTTONDOWN消息后,就接收不到相應的WM_LBUTTONUP消息。
■前面這些規則有兩個例外:
●即使鼠標位于窗口的客戶區之外,窗口過程也有辦法“捕獲鼠標”,并且繼續接收鼠標消息。本章會在后面講述如何捕獲鼠標。
●若正在顯示一個系統模式消息框或系統模式對話框,則其他任何程序都不能接收鼠標消息。當系統模式消息框或對話框處于活動狀態時,它們會阻止系統切換到另一個窗口。例如,關閉Windows時彈出的消息框就是一個系統模式消息框。
6.2.2 第35練:客戶區鼠標消息的處理
/*---------------------------------------------------------------
035? WIN32 API 每日一練
???? 第35個例子CONNECT.C:客戶區鼠標消息的處理
???? SetPixel函數
???? SetCursor函數
???? ShowCursor函數
???? WM_LBUTTONDOWNE消息
???? WM_MOUSEMOVE消息
???? WM_LBUTTONUP消息
(c) www.bcdaren.com, 2020
----------------------------------------------------------------*/
#include <windows.h>
#define MAXPOINTS 1000
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
?PSTR szCmdLine, int iCmdShow)
{
???? static TCHAR szAppName[] = TEXT("Connect");
??? …(略)
???? return msg.wParam;
}
LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM
lParam)
{
???? static POINT pt[MAXPOINTS];//鼠標經過窗口區像素點坐標數組
???? static int iCount;
???? HDC hdc;
???? int i,j;
???? PAINTSTRUCT ps;
???? switch (message)
???? {
??? ?/*測試:非客戶區消息
??? ?//用于通知應用程序在非客戶區(Non-Client Area)
//接收到鼠標消息時進行的命中測試(Hit Test)。
??? ?case WM_NCHITTEST:
??????? ?//直接返回位置信息,阻止系統向所有窗口客戶區和非窗口客戶區發送鼠標消息
??????? ?return (LRESULT)HTNOWHERE;
??? ?//測試:按下ALT+F、Ctrl+C等系統消息
??? ?case WM_SYSKEYDOWN:
??????? //直接返回,使所有系統鍵盤消息失效
??????? ?return 0;*/
???? //按下鼠標左鍵消息
???? case WM_LBUTTONDOWN:????
????????? iCount = 0;
????????? InvalidateRect(hwnd,NULL,TRUE);//重繪窗口---清除背景
????????? return 0;
???? //鼠標移動消息
???? case WM_MOUSEMOVE:??
????????? //按下鼠標左鍵并且iCount小于1000
????????? if (wParam & MK_LBUTTON && iCount < 1000)
????????? {
?????????????? //填充坐標數組
?????????????? pt[iCount].x = LOWORD(lParam);
?????????????? pt[iCount++].y = HIWORD(lParam);
?????????????? hdc = GetDC(hwnd);
??????????? ?? //設置像素點顏色,RGB(0)黑色
?????????????? SetPixel(hdc,LOWORD(lParam),HIWORD(lParam),0);
?????????????? ReleaseDC(hwnd,hdc);
????????? }
????????? return 0;
???? //松開鼠標左鍵消息
???? case WM_LBUTTONUP:?
??????? ? //重新繪制窗口---不清除背景,保留WM_MOUSEMOVE里畫下的點。
????????? InvalidateRect(hwnd,NULL,FALSE);
????????? return 0;
???? case WM_PAINT:
????????? hdc = BeginPaint(hwnd,&ps);
????????? SetCursor(LoadCursor(NULL,IDC_WAIT));//設置鼠標形狀為等待狀態
????????? ShowCursor(TRUE);//顯示鼠標
????????? //像素點之間畫線
????????? for (i = 0;i < iCount - 1;i++)
????????? {
?????????????? for (j = 0;j < iCount - 1;j++)
?????????????? {
??????????????????? MoveToEx(hdc,pt[i].x,pt[i].y,NULL);
??????????????????? LineTo(hdc,pt[j].x,pt[j].y);
?????????????? }
????????? }
????????? ShowCursor(FALSE);//隱藏鼠標
????????? SetCursor(LoadCursor(NULL,IDC_ARROW));//設置鼠標位圖“箭頭形狀”
????????? EndPaint(hwnd,&ps);
????????? return 0;
???? case WM_DESTROY:
????????? PostQuitMessage(0);
????????? return 0;
???? }
???? return DefWindowProc(hwnd, message, wParam, lParam);
}
/***************************************************************************
SetPixel函數:指定坐標到指定的顏色設置像素
COLORREF SetPixel(
? HDC????? hdc,
? int????? x,? //坐標
? int????? y,
? COLORREF color??? //RGB顏色
);
***************************************************************************
SetCursor函數:設置鼠標形狀
HCURSOR SetCursor(
? HCURSOR hCursor?? //IDC_ARROW,IDC_WAIT
);
ShowCursor函數:顯示/隱藏鼠標
int ShowCursor(
? BOOL bShow?? //TRUE顯示,FALSE隱藏
);
***************************************************************************
WM_LBUTTONDOWNE消息:當光標在窗口的客戶區域中時用戶按下鼠標左鍵時發布。
如果未捕獲鼠標,則消息將發布到光標下方的窗口。否則,該消息將發布到捕獲鼠標的窗口中。
參數wParam:指示各種虛擬鍵是否按下。此參數可以是以下一個或多個值。
MK_CONTROL? 0x0008? CTRL鍵按下。
MK_LBUTTON? 0x0001? 鼠標左鍵按下。
MK_MBUTTON? 0x0010? 鼠標中鍵按下。
MK_RBUTTON? 0x0002? 鼠標右鍵按下。
MK_SHIFT??? 0x0004? SHIFT鍵按下。
MK_XBUTTON1 0x0020? 第一個X按鈕按下。
MK_XBUTTON2 0x0040? 第二個X按鈕按下。
lParam:
低位字指定光標的x坐標。坐標相對于客戶區域的左上角。
高階字指定光標的y坐標。坐標相對于客戶區域的左上角。
返回值
如果應用程序處理此消息,則應返回零。
***************************************************************************
WM_MOUSEMOVE消息:光標移動時張貼到窗口。如果未捕獲鼠標,則消息將發布到包含光標的窗口中。否則,該消息將發布到捕獲鼠標的窗口中。
參數與WM_LBUTTONDOWNE消息相同
***************************************************************************
WM_LBUTTONUP消息:當光標在窗口的客戶區域中時,用戶釋放鼠標左鍵時發布。
如果未捕獲鼠標,則消息將發布到光標下方的窗口。否則,該消息將發布到捕獲鼠標的窗口中。
參數與WM_LBUTTONDOWNE消息相同
*/
?????? 運行結果:
圖6-1 客戶區鼠標消息
?
總結
●實例操作方法:
1.第一種——在客戶區按下左鍵,略微移動,再松開左鍵。
2.第二種——在客戶區按下左鍵,快速移動鼠標。
●己知的問題:在客戶區外釋放左鍵,Connnect不會連接這些點,因為沒收到WM_LBUTTONUP消息。
●該程序較耗時,繪制時,鼠標變沙漏形,處理WM_PAINT完后回原來的狀態。用SetCursor來切換鼠標。ShowCursor隱藏或顯示鼠標指針。
●窗口過程:
1.實例CONNECT.C處理了三個鼠標消息。
WM_LBUTTONDOWNE消息:按下鼠標左鍵時,調用InvalidateRect函數清除背景,重繪窗口。
WM_MOUSEMOVE消息:移動鼠標時,采集不超過1000個鼠標移動坐標點,保存在pt數組中,然后使用SetPixel函數繪制坐標點(系統默認黑色畫筆)。
WM_LBUTTONUP消息:松開鼠標左鍵時,重繪窗口,但是并不清除背景。
2.處理WM_PAINT消息時,CONNECT程序需要耗費一定的時間來繪制直線,因此鼠標指針會變成等待位圖。調用SetCursor函數,加載并設置鼠標位圖為等待位圖,顯示鼠標位圖。接著使用雙循環將所有坐標點連接起來。然后再恢復原鼠標位圖。
3.在用戶釋放左鍵時,如果鼠標指針已經移出客戶區,CONNECT程序就不會連接這些點, 因為程序沒有接收到WM_LBUTTONUP消息。此時如果再將鼠標移入客戶區,并按下左鍵,CONNECT程序就會清空客戶區。如果想在客戶區外釋放鼠標,并繼續設計圖形,就可以在客戶區外按下鼠標的左鍵,再將鼠標移入客戶區。
4.動手實驗:處理WM_NCHITTEST消息時可以直接返回鼠標位置信息,阻止系統向所有窗口客戶區和非窗口客戶區發送鼠標消息。
處理WM_SYSKEYDOWN消息時,可以讓所有系統鍵盤消息失效。
下一節我們講述如何在非窗口客戶區捕捉鼠標消息。