《Windows32 SDK教程》10章 用鼠标作图


一、捕获、释放光标

上一章的程序在鼠标控制方面还有问题,之所以用它作例子,完全是为了让大家将鼠标事件搞明白,这一章,我们就来纠正这些问题点。首先要掌握ClipCursor()函数,这个函数将可以将光标限定在窗口有效区内,注意:这个函数的参数用的矩形座标是整个屏幕的座标,这是因为鼠标是全屏动作的。因此,我们还必须学会将窗口有效区座标转换为屏幕座标。ClientToScreen()函数可以实现这一目的,不过这一函数在MFC中的参数和SDK中的参数不一样,MFC中转换的是一矩形块,而SDK转换的是一个点。所以,SDK编程中,下面例子是先转换窗口有效区的(0,0)点,再计算出矩形右下角的点。

BOOL ClientToScreen(                        BOOL ClipCursor(
    HWND hWnd,       //窗口句柄                   const RECT *lpRect    //屏幕座标的矩形块
    LPPOINT lpPoint  //屏幕座标的点           );                        //参数为NULL时为取消该功能
);
LONG clsCur;
bool bDrawing = false;   //画图状态为true
POINTS start;   //开始点

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{;
    switch (message)
    {
        case WM_LBUTTONDOWN:
            bDrawing = true;   //描画状态开始
            start = MAKEPOINTS(lParam); //开始点保存于start中

            //取得窗口有效区
            RECT rect;
            GetClientRect(hWnd,&rect);

            //转换窗口有效区为屏幕座标
            POINT point;
            point.x = 0, point.y = 0;
            ClientToScreen(hWnd,&point);
            rect.top = point.y;
            rect.left = point.x;
            rect.bottom += rect.top;
            rect.right += rect.left;

            //将光标限定在窗口有效区内
            ClipCursor(&rect);  //rect一定要是屏幕座标

            if (!clsCur) {
                clsCur = GetClassLong(hWnd,GCL_HCURSOR);  //取当前窗口的光标
            }
            SetClassLong(hWnd,GCL_HCURSOR,NULL);  //设置当前窗口的光标为NULL
            SetCursor(LoadCursor(NULL, IDC_CROSS)); //设置当前光标为十字形
            break;
        case WM_MOUSEMOVE:
            HDC hdc;
            hdc = GetDC(hWnd);

            if (bDrawing) {
                MoveToEx(hdc, start.x, start.y,NULL);
                LineTo(hdc, LOWORD(lParam), HIWORD(lParam));
            }

            ReleaseDC(hWnd, hdc);
            break;
        case WM_LBUTTONUP:
            bDrawing = false;    //描画状态结束
            ClipCursor(NULL);  //使光标可以在屏幕任意位置移动
            SetClassLong(hWnd,GCL_HCURSOR, clsCur);  //设置窗口光标为原先的光标
            SetCursor((HCURSOR)clsCur);  //设置当前光标为原先的光标
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
   }
   return 0;
}

二、消除移动轨迹

上例中,想要画一条线(即鼠标按下到松开的一条线),结果却将鼠标移动的“轨迹”画了出来,这些“轨迹”必须擦掉。VC不提供擦掉已经画好的图案的功能,所谓“擦掉”,就是选用某种方式重画,使得看上去象重画一样。通过SetROP2()函数就可以实现这个功能,这个函数将反转屏幕颜色,这样第二次画时就恢复为原来的颜色。注意:SetROP2()函数这是设置画图模式的函数,是设置前景与背景的关系。

int SetROP2(
    HDC hdc,         // HDC
    int fnDrawMode   //描画模式
);

说明
R2_BLACK象素颜色始终为0(即黑色)
R2_WHITE象素颜色始终为1(即白色)
R2_COPYPEN象素为画笔的颜色
R2_NOTCOPYPEN象素为画笔相反的颜色
R2_MASKNOTPENPixel is a combination of the colors common to both the screen and the inverse of the pen.
R2_MASKPENPixel is a combination of the colors common to both the pen and the screen.
R2_MASKPENNOTPixel is a combination of the colors common to both the pen and the inverse of the screen.
R2_MERGENOTPENPixel is a combination of the screen color and the inverse of the pen color.
R2_MERGEPENPixel is a combination of the pen color and the screen color.
R2_MERGEPENNOTPixel is a combination of the pen color and the inverse of the screen color.
R2_NOP象素不发生任何改变
R2_NOT象素反转
R2_NOTMASKPENPixel is the inverse of the R2_MASKPEN color.
R2_NOTMERGEPENPixel is the inverse of the R2_MERGEPEN color.
R2_XORPEN象素为当前象素与画笔“异或”的值
R2_NOTXORPEN象素为当前象素与画笔“异或”的值再“取反”

LONG clsCur;
bool bDrawing = false;   //画图状态为true
POINTS start, end, prev;   //开始点、结束点、前一个移动点

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{;
    PAINTSTRUCT ps;
    HDC hdc;
    RECT rc;

    switch (message)
    {
        case WM_LBUTTONDOWN:
            bDrawing = true;   //描画状态开始
            prev = start = MAKEPOINTS(lParam); //开始点保存于start和prev中

            //取得窗口有效区
            RECT rect;
            GetClientRect(hWnd,&rect);

            //转换窗口有效区为屏幕座标
            POINT point;
            point.x = 0, point.y = 0;
            ClientToScreen(hWnd, &point);
            rect.top = point.y;
            rect.left = point.x;
            rect.bottom += rect.top;
            rect.right += rect.left;

            //将光标限定在窗口有效区内
            ClipCursor(&rect);

            if (!clsCur) {
                clsCur = GetClassLong(hWnd,GCL_HCURSOR);
            }
            SetClassLong(hWnd,GCL_HCURSOR,NULL);  //设置当前窗口的光标为NULL
            SetCursor(LoadCursor(NULL, IDC_CROSS)); //设置当前光标为十字形

            break;
        case WM_MOUSEMOVE:
            if (bDrawing) {    //如果是描画状态
                HDC hdc;
                hdc = GetDC(hWnd);

                end = MAKEPOINTS(lParam);  //当前点保存于end中
                Draw(hdc, start, prev); //擦去前一次所画内容
                Draw(hdc, start, end); //画开始点到当前点
                prev = end;  //当前点设置为旧的点

                ReleaseDC(hWnd, hdc);
            }
            break;
        case WM_LBUTTONUP:
            bDrawing = false;    //描画状态结束
            ClipCursor(NULL);
            SetClassLong(hWnd,GCL_HCURSOR, clsCur);
            SetCursor((HCURSOR)clsCur);
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
   }
   return 0;
}

//作图
void Draw(HDC hdc, POINTS beg, POINTS end)
{
    SetROP2(hdc, R2_NOT);   //设置描画方式为象素反转
    MoveToEx(hdc, beg.x, beg.y, NULL);  //移动描画点到按下鼠标键的地方
    LineTo(hdc, end.x, end.y); //从描画点到(end.x, end.y)点
    
    return;
} 

三、鼠标松开后的处理

上例画线部分基本可以说大功告成,但多画几条交叉线,仔细一看便发现交差点有的地方变成空白。原因其实很简单,第一根线为黑色,第二根线在交差点处反转后自然是空白。另外,如果最终想画成兰色的粗线的话,该怎么办?

这很简单,只要在鼠标松开时再画一次,这时可以设置颜色和粗细,另外,作图模式也得改为正常模式。

LONG clsCur;
bool bDrawing = false;   //画图状态为true
POINTS start, end, prev;   //开始点、结束点、前一个移动点

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{;
    PAINTSTRUCT ps;
    HDC hdc;
    RECT rc;

    switch (message)
    {
        case WM_LBUTTONDOWN:
            //与上例相同,省略
        case WM_MOUSEMOVE:
            //与上例相同,省略
        case WM_LBUTTONUP:
            hdc = GetDC(hWnd);
            Draw2(hdc, start, end); //画开始点到当前点

            bDrawing = false;    //描画状态结束
            ClipCursor(NULL);
            SetClassLong(hWnd,GCL_HCURSOR, clsCur);
            SetCursor((HCURSOR)clsCur);

            ReleaseDC(hWnd, hdc);
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
   }
   return 0;
}

//作图
void Draw(HDC hdc, POINTS beg, POINTS end)
{
    //与上例相同,省略
} 

void Draw2(HDC hdc, POINTS beg, POINTS end)
{
    HPEN hPen, hOldPen;
    SetROP2(hdc, R2_COPYPEN);   //设置前景配色方式为画笔颜色

    hPen = CreatePen(PS_SOLID, 5, RGB(0, 100, 255));  //兰色实线画笔
    hOldPen = (HPEN)SelectObject(hdc, hPen);

    MoveToEx(hdc, beg.x, beg.y, NULL);  //移动描画点到按下鼠标键的地方
    LineTo(hdc, end.x, end.y); //从描画点到(end.x, end.y)点

    SelectObject(hdc, hOldPen);
    DeleteObject(hPen);
    
    return;
}