《Windows32 SDK教程》09章 鼠标消息处理


鼠标操作是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。

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;
}