《Windows32 SDK教程》11章 用鼠标画圆和椭圆


这一章比较简单,是画封闭图形的例子,画圆、画方框等都是同样原理,要注意的是背景的设置。

一、画圆和椭圆

上一章的程序的基础上,将画线语句换成画圆的语句就可以了。画圆是用Ellipse()函数。

BOOL Ellipse(
  HDC hdc,          //HDC
  int nLeftRect,    //外切矩形的“左上”x座标
  int  nTopRect,    //外切矩形的“左上”y座标
  int  nRightRect,  //外切矩形的“右下”x座标
  int  nBottomRect  //外切矩形的“右下”y座标
);
LONG clsCur;
bool bDrawing = false;   //画图状态为true
POINTS start, end, prev;  //开始点、结束点、前一个移动点

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        //与上例相同,省略
    }
    return 0;
}

//作图
void Draw(HDC hdc, POINTS beg, POINTS end)
{
    SetROP2(hdc, R2_NOT);   //设置前景配色方式为象素反转
    Ellipse(hdc, beg.x, beg.y, end.x, end.y);  //画圆

    return;
} 

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

    Ellipse(hdc, beg.x, beg.y, end.x, end.y);  //画圆

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

二、消除拖动时的反转背景

上面的例子导热一个问题,就是在拖动鼠标画圆时,圆圈里的颜色也被反转了。这是SetROP2(hdc, R2_NOT)函数造成的,也是画封闭图形必然遇到的问题。解决办法很简单,画图前将画刷设置为“空画刷”,画完后恢复为原来的画刷。下例中的HOLLOW_BRUSH与NULL_BRUSH相同。

void Draw(HDC hdc, POINTS beg, POINTS end)
{
    HBRUSH hOldBrush;
    hOldBrush = (HBRUSH)SelectObject(hdc, GetStockObject(HOLLOW_BRUSH)); //注意:选用空画刷

    SetROP2(hdc, R2_NOT);   //设置前景配色方式为象素反转
    Ellipse(hdc, beg.x, beg.y, end.x, end.y);  //画圆

    SelectObject(hdc, hOldBrush);       //恢复旧刷
    return;
} 

void Draw2(HDC hdc, POINTS beg, POINTS end)
{
    HPEN hPen, hOldPen;
    HBRUSH hOldBrush;

    SetROP2(hdc, R2_COPYPEN);   //设置前景配色方式为画笔颜色

    hPen = CreatePen(PS_SOLID, 5, RGB(0, 100, 255));  //兰色实线画笔
    hOldPen = (HPEN)SelectObject(hdc, hPen);
    hOldBrush = (HBRUSH)SelectObject(hdc, GetStockObject(HOLLOW_BRUSH)); //注意:选用空画刷

    Ellipse(hdc, beg.x, beg.y, end.x, end.y);  //画圆

    SelectObject(hdc, hOldPen);
    DeleteObject(hPen);
    SelectObject(hdc, hOldBrush);       //恢复旧刷
    
    return;
} 

三、画正圆

上面画的其实是椭圆,如果要画正圆,只要比较外切矩形的长和宽,一般以短边为正方形边长。

void Draw(HDC hdc, POINTS beg, POINTS end)
{
    int x, y, z;
    HBRUSH hOldBrush;
    hOldBrush = (HBRUSH)SelectObject(hdc, GetStockObject(HOLLOW_BRUSH)); //注意:选用空画刷

    //计算外切正方形
    x = abs(beg.x - end.x), y = abs(beg.y - end.y);
    z = min(x, y);        //正方形的边长
    if (end.x > beg.x) {
        x = beg.x + z;
    } else {
        x = beg.x - z;
    }
    if (end.y > beg.y) {
        y = beg.y + z;
    } else {
        y = beg.y - z;
    }

    SetROP2(hdc, R2_NOT);   //设置前景配色方式为象素反转
    Ellipse(hdc, beg.x, beg.y, x, y);  //画圆

    SelectObject(hdc, hOldBrush);       //恢复旧刷
    return;
} 

void Draw2(HDC hdc, POINTS beg, POINTS end)
{
    int x, y, z;
    HPEN hPen, hOldPen;
    HBRUSH hOldBrush;

    //计算外切正方形
    x = abs(beg.x - end.x), y = abs(beg.y - end.y);
    z = min(x, y);        //正方形的边长
    if (end.x > beg.x) {
        x = beg.x + z;
    } else {
        x = beg.x - z;
    }
    if (end.y > beg.y) {
        y = beg.y + z;
    } else {
        y = beg.y - z;
    }

    SetROP2(hdc, R2_COPYPEN);   //设置前景配色方式为画笔颜色

    hPen = CreatePen(PS_SOLID, 5, RGB(0, 100, 255));  //兰色实线画笔
    hOldPen = (HPEN)SelectObject(hdc, hPen);
    hOldBrush = (HBRUSH)SelectObject(hdc, GetStockObject(HOLLOW_BRUSH)); //注意:选用空画刷

    Ellipse(hdc, beg.x, beg.y, x, y);  //画圆

    SelectObject(hdc, hOldPen);
    DeleteObject(hPen);
    SelectObject(hdc, hOldBrush);       //恢复旧刷
    
    return;
} 

四、经过鼠标当前点的圆

上面的例子中,拖动鼠标画圆时,鼠标点为外切矩形的右下角(也可能是左上角)。如果要画一条经过当前点的圆,则首先要计算出开始点到当前点的距离,并作为半径,从而计算出外切正方形的位置。

void Draw(HDC hdc, POINTS beg, POINTS end)
{
    double x, y;
    int r;
    HBRUSH hOldBrush;
    hOldBrush = (HBRUSH)SelectObject(hdc, GetStockObject(HOLLOW_BRUSH)); //注意:选用空画刷

    //计算半径
    x = beg.x - end.x, y = beg.y - end.y;
    r = (int)sqrt(x * x + y * y);

    SetROP2(hdc, R2_NOT);   //设置前景配色方式为象素反转
    Ellipse(hdc, beg.x - r, beg.y-r, beg.x + r, beg.y + r);  //画圆

    SelectObject(hdc, hOldBrush);       //恢复旧刷
    return;
} 

void Draw2(HDC hdc, POINTS beg, POINTS end)
{
    double x, y;
    int r;
    HPEN hPen, hOldPen;
    HBRUSH hOldBrush;

    //计算半径
    x = beg.x - end.x, y = beg.y - end.y;
    r = (int)sqrt(x * x + y * y);

    SetROP2(hdc, R2_COPYPEN);   //设置前景配色方式为画笔颜色

    hPen = CreatePen(PS_SOLID, 5, RGB(0, 100, 255));  //兰色实线画笔
    hOldPen = (HPEN)SelectObject(hdc, hPen);
    hOldBrush = (HBRUSH)SelectObject(hdc, GetStockObject(HOLLOW_BRUSH)); //注意:选用空画刷

    Ellipse(hdc, beg.x - r, beg.y-r, beg.x + r, beg.y + r);  //画圆

    SelectObject(hdc, hOldPen);
    DeleteObject(hPen);
    SelectObject(hdc, hOldBrush);       //恢复旧刷
    
    return;
} 

五、优化

上面的例子是一步一步演化而来,将画图部分提取出来,


void doDraw(HDC hdc, POINTS beg, POINTS end)
{
    double x, y;
    int r;

    //计算半径
    x = beg.x - end.x, y = beg.y - end.y;
    r = (int)sqrt(x * x + y * y);

    Ellipse(hdc, beg.x - r, beg.y-r, beg.x + r, beg.y + r);  //画圆
}

void Draw(HDC hdc, POINTS beg, POINTS end)
{
    HBRUSH hOldBrush;
    hOldBrush = (HBRUSH)SelectObject(hdc, GetStockObject(HOLLOW_BRUSH)); //注意:选用空画刷
    SetROP2(hdc, R2_NOT);   //设置前景配色方式为象素反转

    doDraw(hdc, beg, end);

    SelectObject(hdc, hOldBrush);       //恢复旧刷
    return;
} 

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

    hPen = CreatePen(PS_SOLID, 5, RGB(0, 100, 255));  //兰色实线画笔
    hOldPen = (HPEN)SelectObject(hdc, hPen);
    hOldBrush = (HBRUSH)SelectObject(hdc, GetStockObject(HOLLOW_BRUSH)); //注意:选用空画刷

    doDraw(hdc, beg, end);

    SelectObject(hdc, hOldPen);
    DeleteObject(hPen);
    SelectObject(hdc, hOldBrush);       //恢复旧刷
    
    return;
}