滑鼠訊息處理
滑鼠操作是Windows的重要內容,為了下一章用滑鼠作圖,本章先作一些基礎知識的鋪墊,以免下一章新內容太多。前面章節已經說過,視窗函式總共處理訊息結構體中的三個內容,即message、wParam、lParam,其中message是主訊息,wParam、lParam許多場合都不用,也就是值為0。有的訊息只體用wParam、lParam的一個,不過鍵盤滑鼠訊息傳遞的資訊比較多,wParam、lParam這兩個副訊息全用上了。
一、剖析滑鼠訊息WM_LBUTTONDOWN、WM_LBUTTONUP、WM_MOUSEMOVE是滑鼠的三個基本訊息處理,分別表示滑鼠左鍵按下、滑鼠左鍵鬆開、滑鼠移動,這三個主訊息的副訊息的內容完全相同,下面小雅用WM_MOUSEMOVE的二個副訊息為例,來剖析其內容究竟是什麼。
lParam副訊息存放的是滑鼠的座標位置,位元組的低4位為x座標,高4位為y座標。用位操作符&很容易就取到滑鼠的x和y座標,VC也提供了巨集HIWORD()和LOWORD()。wParam的高4位不用,低4位表示組合鍵的使用狀態。第1位為“1”表示滑鼠左鍵按下,第2位為“1”表示滑鼠右鍵按下,第3位為“1”表示Shift鍵按下,第4位為“1”表示Ctrl鍵按下,這4種狀態可以組合使用。例如,滑鼠左右鍵同時按下,wParam為3;滑鼠左右鍵和Shift、Ctrl鍵全部按下則wParam為F。
1(Ctrl)1(Shift)1(右鍵)1(左鍵)
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {switch
(message) {case
WM_MOUSEMOVE: HDC hdc; RECT rc;char
strx[64];char
strAction[64];int
xPos, yPos; xPos = LOWORD(lParam); yPos = HIWORD(lParam); wsprintf((LPSTR)strx, "滑鼠位置: (%3d, %3d) wParam=%X ",xPos, yPos, wParam); hdc = GetDC(hWnd); GetClientRect(hWnd, &rc); TextOut(hdc,10,10,strx, (int
)strlen(strx));if
(wParam & 0x1) { wsprintf((LPSTR)strAction, "滑鼠左鍵: ON "); }else
{ wsprintf((LPSTR)strAction, "滑鼠左鍵: OFF"); } TextOut(hdc, 10, 25, strAction, (int
)strlen(strAction));if
(wParam & 0x2) { wsprintf((LPSTR)strAction, "滑鼠右鍵: ON "); }else
{ wsprintf((LPSTR)strAction, "滑鼠右鍵: OFF"); } TextOut(hdc, 10, 40, strAction, (int
)strlen(strAction));if
(wParam & 0x4) { wsprintf((LPSTR)strAction, "Shift鍵: ON "); }else
{ wsprintf((LPSTR)strAction, "Shift鍵: OFF"); } TextOut(hdc, 10, 55, strAction, (int
)strlen(strAction));if
(wParam & 0x8) { wsprintf((LPSTR)strAction, "Ctrl鍵: ON "); }else
{ wsprintf((LPSTR)strAction, "Ctrl鍵: OFF"); } TextOut(hdc, 10, 70, strAction, (int
)strlen(strAction)); ReleaseDC(hWnd, hdc);break
;case
WM_DESTROY: PostQuitMessage(0);break
;default
:return
DefWindowProc(hWnd, message, wParam, lParam); }return
0; }
二、滑鼠游標形狀的改變
作圖時,習慣上按下滑鼠左鍵時游標變為“十字形”,鬆開後恢復為預設的“箭頭”,因此在WM_LBUTTONDOWN訊息處理和WM_LBUTTONUP訊息處理中要增加對滑鼠游標的控制。
游標都是由視窗類最初設定的,一般為箭頭,但有人會改變成自己喜好的游標。視窗要控制游標首先要取當前游標的型別並儲存,然後取消視窗對游標的控制,並設定游標為“十字形”,這些都是在WM_LBUTTONDOWN的訊息處理程式中實現的。在WM_LBUTTONUP的訊息處理程式中,還必須恢復視窗對游標的控制,並立即讓游標成為視窗預設的游標。
設定游標是用SetCursor()函式,因其簡單,不作解釋應該沒有問題。取當前系統游標的型別是用GetClassLong()函式,設定系統游標的型別是用SetClassLong()函式,這2個函式很重要,並不僅僅針對游標,根據引數的不同,可以取得或設定視窗型別、背景、圖示、游標、選單等等,這些全是註冊視窗類時設定的內容。
DWORD GetClassLong( DWORD SetClassLong( HRESULT SetCursor( HWND hWnd, HWND hWnd, //HDC LONG lPartIDint
nIndexint
nIndex, //型別 ); ); LONG dwNewLong //設定值 );
HCURSOR clsCur; LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {switch
(message) {case
WM_LBUTTONDOWN: clsCur = (HCURSOR)GetClassLong(hWnd,GCL_HCURSOR); //取當前的游標值 SetClassLong(hWnd,GCL_HCURSOR,NULL); //關閉視窗類對游標的控制 SetCursor(LoadCursor(NULL, IDC_CROSS)); //設定游標為十字形break
;case
WM_MOUSEMOVE: //(與上例相同,省略)break
;case
WM_LBUTTONUP: SetClassLong(hWnd,GCL_HCURSOR,(LONG)clsCur); //恢復視窗類對游標的控制 SetCursor(LoadCursor(NULL, IDC_ARROW)); //恢復箭頭游標break
;case
WM_DESTROY: PostQuitMessage(0);break
;default
:return
DefWindowProc(hWnd, message, wParam, lParam); }return
0; }
有人問,在WM_LBUTTONUP中既然恢復了視窗類對游標的控制,為什麼還要下面一句設定游標?其實SetCursor()函式省略也可以,但鬆開左鍵後如果不移動,游標不會改變,因為沒有訊息傳過來。不過,上面程式是有以下BUG!
- 當游標超出視窗時,游標不正確。
- 當游標超出視窗時鬆開左鍵,游標不能恢復正常。
- 當視窗預設游標不是“箭頭”時,鬆開左鍵時變成了“箭頭”。
上例的BUG中,設定游標為“十字形”應該放在WM_MOUSEMOVE處理中便解決了,游標不能恢復正常是因為未受到WM_LBUTTONUP訊息。注意:這時一定要區分是滑鼠左鍵是否按下。因為按下和未按下游標不一樣。另外,“SetCursor(LoadCursor(NULL, IDC_ARROW));”這一句是錯誤的,因為視窗游標不一定是“箭頭”。正確寫法是SetCursor((HCURSOR)clsCur); 本章故意將視窗游標設定成“問號”。
下面我們在更正BUG的同時,將滑鼠左芻按下的點到鬆開點畫一條直線。畫直線是用MoveToEx()函式先移動到起點,再用LineTo()函式畫到終點。這樣,便產生了一個你所預想之外的效果。
我們已經知道定義一個矩形塊變數用結構體RECT,基中有4個座標值分別表示左上角的x和y、右下角的x和y。同樣點是用結構體POINT來表示的,其中有2個座標值表示點的x和y。要實現下面的功能,必須事先儲存好起始點。
LONG clsCur;bool
bDrawing; POINTS point; LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {switch
(message) {case
WM_LBUTTONDOWN:if
(!clsCur) { clsCur = GetClassLong(hWnd,GCL_HCURSOR); } SetClassLong(hWnd,GCL_HCURSOR,NULL); bDrawing =true
; point.x = LOWORD(lParam); point.y = HIWORD(lParam);break
;case
WM_MOUSEMOVE: HDC hdc; RECT rc;char
strx[64];char
strAction[64];int
xPos, yPos; xPos = LOWORD(lParam); yPos = HIWORD(lParam); wsprintf((LPSTR)strx, "滑鼠位置: (%3d, %3d) wParam=%X ",xPos, yPos, wParam); hdc = GetDC(hWnd); GetClientRect(hWnd, &rc); TextOut(hdc,10,10,strx, (int
)strlen(strx));if
(wParam & 0x1) { wsprintf((LPSTR)strAction, "滑鼠左鍵: ON "); }else
{ wsprintf((LPSTR)strAction, "滑鼠左鍵: OFF"); } TextOut(hdc, 10, 25, strAction, (int
)strlen(strAction));if
(wParam & 0x2) { wsprintf((LPSTR)strAction, "滑鼠右鍵: ON "); }else
{ wsprintf((LPSTR)strAction, "滑鼠右鍵: OFF"); } TextOut(hdc, 10, 40, strAction, (int
)strlen(strAction));if
(wParam & 0x4) { wsprintf((LPSTR)strAction, "Shift鍵: ON "); }else
{ wsprintf((LPSTR)strAction, "Shift鍵: OFF"); } TextOut(hdc, 10, 55, strAction, (int
)strlen(strAction));if
(wParam & 0x8) { wsprintf((LPSTR)strAction, "Ctrl鍵: ON "); }else
{ wsprintf((LPSTR)strAction, "Ctrl鍵: OFF"); } TextOut(hdc, 10, 70, strAction, (int
)strlen(strAction));if
(bDrawing) { SetCursor(LoadCursor(NULL, IDC_CROSS)); //設定游標為十字形 MoveToEx(hdc, point.x, point.y,NULL); LineTo(hdc, LOWORD(lParam), HIWORD(lParam)); } ReleaseDC(hWnd, hdc);break
;case
WM_LBUTTONUP: bDrawing =false
; SetClassLong(hWnd,GCL_HCURSOR, clsCur); SetCursor((HCURSOR)clsCur);break
;case
WM_DESTROY: PostQuitMessage(0);break
;default
:return
DefWindowProc(hWnd, message, wParam, lParam); }return
0; }