1. 程式人生 > >滑鼠訊息處理

滑鼠訊息處理

滑鼠操作是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 lPartID
    int nIndex                  int 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!

  1. 當游標超出視窗時,游標不正確。
  2. 當游標超出視窗時鬆開左鍵,游標不能恢復正常。
  3. 當視窗預設游標不是“箭頭”時,鬆開左鍵時變成了“箭頭”。
三、移動滑鼠時畫線

上例的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;
}