Search code examples
c++visual-c++bitblt

Having trouble putting reusable code into a subroutine


I am trying to understand better the quirks of Visual C++. As such, I have come to the stage where I make a program that starts out as an empty window, but as you click around, a check board of red and blue squares emerges (it's not good on the eyes, but it works). The squares also alternate between the two colours if you click them again. Each square is 100x100 pixels, and I have in my project folder the image files for them (I know I could use the last two integer agruments of BitBlt to use a single image that is half-blue, half-red, and therefore not need toPaint, but that's not the issue here)

This is what my painting routine looks like right now (and it works fine, whatever's not declared here is a global variable):

case WM_PAINT:
    {
    // 'HDC hdc' declared before the switch statement
    // 'PAINTSTRUCT ps' declared before the switch statement
    // 'hWnd' is the first argument to WndProc()
    hdc = BeginPaint(hWnd, &ps); 

    HBITMAP toPaint = NULL;
    BITMAP bm;

    // 'xcor' and 'ycor' are the coordinates of the last left-click
    // 'int counters[][]' keeps track of the number of left-clicks in each square
    // 'blue' and 'red' are HBITMAPs initialized in case WM_CREATE
    if (counters[xcor / 100][ycor / 100] % 2 == (xcor / 100 + ycor / 100) % 2)
        toPaint = blue;
    else
        toPaint = red;

    HDC hdcMem = CreateCompatibleDC(hdc);
    HGDIOBJ hbmOld = SelectObject(hdcMem, toPaint);
    GetObject(toPaint, sizeof(bm), &bm);
    BitBlt(hdc, xcor - xcor % 100, ycor - ycor % 100, 100, 100, hdcMem, 0, 0, SRCCOPY);
    SelectObject(hdcMem, hbmOld);

    EndPaint(hWnd, &ps);

    break;
    }

Now, whenever I would resize the window, either maximizing, or just dragging the edges, everything would be repainted, and since there is only one left-click stored, it would only draw one square, and the rest would go back to default gray. I therefore decided to catch the WM_SIZE case, and redraw all squares that had been drawn up to that point:

case WM_SIZE:

    hdc = BeginPaint(hWnd, &ps);
    for (int i = 0; i < 20; i++)
    {
        for (int j = 0; j < 20; j++)
        {
            // checks whether that square has been drawn before
            if (counters[i][j] == 0)
                continue;

            HBITMAP toPaint = NULL;
            BITMAP bm;
            if (counters[i][j] % 2 == (i + j) % 2)
                toPaint = blue;
            else
                toPaint = red;

            HDC hdcMem = CreateCompatibleDC(hdc);
            HGDIOBJ hbmOld = SelectObject(hdcMem, toPaint);
            GetObject(toPaint, sizeof(bm), &bm);
            BitBlt(hdc, i*100, j*100, 100, 100, hdcMem, 0, 0, SRCCOPY);
            SelectObject(hdcMem, hbmOld);


        }
    }
    EndPaint(hWnd, &ps);

    break;

As you can see, everything inside the innermost for-loop, after the first if-test, is more or less an exact copy of what I had in my WM_PAINT case, which I would think is a good sign that those lines should be put into its own function call, something like DrawSquare(HWND hWnd, int i, int j, HDC handle, PAINTSTRUCT ps). However, I can't figure out how to navigate all the pointers, references and copies. I can get something that compiles, but then it won't draw anything.

How would I write such a DrawSquare()?


Solution

  • Well, that was easier than I anticipated. Using

    void DrawSquare(HWND hWnd, HDC hdc, int x, int y)
    {
        HBITMAP toPaint = NULL;
        BITMAP bm;
        if (counters[x][y] % 2 == (x + y) % 2)
            toPaint = blue;
        else
            toPaint = red;
    
        HDC hdcMem = CreateCompatibleDC(hdc);
        HGDIOBJ hbmOld = SelectObject(hdcMem, toPaint);
        GetObject(toPaint, sizeof(bm), &bm);
        BitBlt(hdc, x * 100, y * 100, 100, 100, hdcMem, 0, 0, SRCCOPY);
        SelectObject(hdcMem, hbmOld);
    
        // Thanks to the comment below, to avoid memory leak
        DeleteDC(hdcMem);
    }
    

    without the PAINTSTRUCT worked just fine (since I'm not explicitly using it). Now my cases look like this:

    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        DrawSquare(hWnd, hdc, xcor / 100, ycor / 100);
    
        EndPaint(hWnd, &ps);
    
        break;
    
    case WM_SIZE:
    
        hdc = BeginPaint(hWnd, &ps);
        for (int i = 0; i < 20; i++)
        {
            for (int j = 0; j < 20; j++)
            {
                if (counters[i][j] == 0)
                    continue;
                DrawSquare(hWnd, hdc, i, j);
    
            }
        }
        EndPaint(hWnd, &ps);
    
        break;
    

    which is much better than what I had.