Search code examples
c++winapigdi

[c++]WinApi - Simple Program drawing lines


I'm new and I'm trying to write a simple program drawing lines with the mouse. I have a problem with drawing these lines, because it leaves traces behind.

Here is an image of my problem:

image

And here is a sample of my code:

LRESULT APIENTRY WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_DESTROY:PostQuitMessage(0);break;

        case WM_LBUTTONDOWN:
            hdc = GetDC(hwnd);
            last_x = LOWORD(lParam);
            last_y = HIWORD(lParam);
            isDown = true;
            break;
        case WM_MOUSEMOVE:
            if (isDown)
            {
                Pen = CreatePen(PS_SOLID, 3, RGB(0, 0, 255));
                Box = (HPEN)SelectObject(hdc, Pen);
                int x = LOWORD(lParam);
                int y = HIWORD(lParam);
                MoveToEx(hdc, last_x, last_y, NULL);
                LineTo(hdc, x, y);
            }
            break;
        case WM_LBUTTONUP:
            isDown = false;
            ReleaseDC;
            break;

        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
    return 0;
}

EDIT: Now it's working well, but if You could explain me one another thing, how can i make that my old lines stayed on the Client area when i drawing new lines? Cause now i can draw only one line. Should i use Bitmap to save screen or something?

EDIT:EDIT: Ok i used Vector to save coordinates of every line. Thank You guys for help!


Solution

  • You are getting residual traces because you are not erasing your old drawings before drawing a new line, or at least updating last_x and last_y on each move so that a new line connects to the end of the previous line, like in Microsoft's example.

    But, you really should not draw on the window directly in the mouse message handlers at all. As soon as the window needs a repaint for any reason, all of your drawing will be lost. The correct way to handle this is to perform all of your drawing in a WM_PAINT message handler instead. Use the mouse messages to keep track of line information as needed, and then do all of the actual drawing in WM_PAINT only.

    For example:

    LRESULT APIENTRY WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        switch (uMsg)
        {
            case WM_CREATE:
                Pen = CreatePen(PS_SOLID, 3, RGB(0, 0, 255));
                break;
    
            case WM_DESTROY:
                DeleteObject(Pen);
                PostQuitMessage(0);
                break;
    
            case WM_LBUTTONDOWN:
                x = last_x = LOWORD(lParam);
                y = last_y = HIWORD(lParam);
                isDown = true;
                InvalidateRect(hwnd, NULL, TRUE);
                break;
    
            case WM_MOUSEMOVE:
                if (isDown)
                {
                    x = LOWORD(lParam);
                    y = HIWORD(lParam);
                    InvalidateRect(hwnd, NULL, TRUE);
                }
                break;
    
            case WM_LBUTTONUP:
                isDown = false;
                InvalidateRect(hwnd, NULL, TRUE);
                break;
    
            /* if your WNDCLASS sets hbrBackground=NULL, uncomment this handler...
            case WM_ERASEBKGND:
            {
                HDC hdc = (HDC) wParam; 
                draw a background on the hdc as needed...
                return 1;
            }
            */
    
            case WM_PAINT:
            {
                PAINTSTRUCT ps;
                HDC hdc = BeginPaint(hwnd, &ps);
    
                if (last_x != x) || (last_y != y)
                {
                    HPEN OldPen = (HPEN) SelectObject(hdc, Pen);
                    MoveToEx(hdc, last_x, last_y, NULL);
                    LineTo(hdc, x, y);
                    SelectObject(hdc, OldPen);
                }
    
                EndPaint(hwnd, &ps);
                break;
            }
    
            default:
                return DefWindowProc(hwnd, uMsg, wParam, lParam);
        }
        return 0;
    }
    

    That will draw a single line that starts at the point where the mouse was first held down, and then follow the mouse as it moves around.

    Or, if you want to draw multiple lines end-to-end that follow the mouse while it is held down, try this:

    std::vector<POINT> points;
    
    LRESULT APIENTRY WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        switch (uMsg)
        {
            case WM_CREATE:
                Pen = CreatePen(PS_SOLID, 3, RGB(0, 0, 255));
                points.clear();
                break;
    
            case WM_DESTROY:
                DeleteObject(Pen);
                PostQuitMessage(0);
                break;
    
            case WM_LBUTTONDOWN:
            {
                points.clear();
                POINT pt;
                pt.x = LOWORD(lParam);
                pt.y = HIWORD(lParam);
                points.push_back(pt);
                isDown = true;
                InvalidateRect(hwnd, NULL, TRUE);
                break;
            }
    
            case WM_MOUSEMOVE:
                if (isDown)
                {
                    POINT pt;
                    pt.x = LOWORD(lParam);
                    pt.y = HIWORD(lParam);
                    points.push_back(pt);
                    InvalidateRect(hwnd, NULL, TRUE);
                }
                break;
    
            case WM_LBUTTONUP:
                isDown = false;
                InvalidateRect(hwnd, NULL, TRUE);
                break;
    
            /* if your WNDCLASS sets hbrBackground=NULL, uncomment this handler...
            case WM_ERASEBKGND:
            {
                HDC hdc = (HDC) wParam; 
                draw a background on the hdc as needed...
                return 1;
            }
            */
    
            case WM_PAINT:
            {
                PAINTSTRUCT ps;
                HDC hdc = BeginPaint(hwnd, &ps);
    
                if (points.size() > 1)
                {
                    HPEN OldPen = (HPEN) SelectObject(hdc, Pen);
                    MoveToEx(hdc, points[0].x, points[0].y, NULL);
                    for (size_t i = 1; i < points.size(); ++i) {
                        LineTo(hdc, points[i].x, points[i].y);
                    }
                    SelectObject(hdc, OldPen);
                }
    
                EndPaint(hwnd, &ps);
                break;
            }
    
            default:
                return DefWindowProc(hwnd, uMsg, wParam, lParam);
        }
        return 0;
    }