Search code examples
cwindowswinapigdi

Painting code doesn't work properly in my WM_COMMAND message handler


I have the following code that is supposed to paint picture2.bmp after the user invokes the item ID_FILE_32771.

case WM_COMMAND:
    wmId    = LOWORD(wParam);
    wmEvent = HIWORD(wParam);
    // Parse the menu selections:
    switch (wmId)
    {
    case ID_FILE_32771:
        hdc = BeginPaint(hWnd, &ps);
        LoadAndBlitBitmap2(__T("D://picture2.bmp"), hdc);
        EndPaint(hWnd, &ps);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    break;
case WM_PAINT:
    hdc = BeginPaint(hWnd, &ps);
    LoadAndBlitBitmap(__T("D://picture.bmp"), hdc);
    EndPaint(hWnd, &ps);
    break;

The problem is, nothing is getting painted. hdc is not properly initialized. What am I doing wrong, and how do I fix it?


Solution

  • The BeginPaint and EndPaint functions are only to be called when you handle the WM_PAINT message. The way you've done it, in handling WM_COMMAND, is wrong:

    case ID_FILE_32771:
            hdc = BeginPaint(hWnd, &ps);                        // WRONG, WRONG!
            LoadAndBlitBitmap2(__T("D://picture2.bmp"), hdc);
            EndPaint(hWnd, &ps);                                // ALSO WRONG!
            break;
    

    All of the painting code needs to be inside of the WM_PAINT handler. You are going to receive WM_PAINT messages whenever you window needs to be painted, and sometimes that's outside of your control. So there needs to be logic in the WM_PAINT handler that knows what to paint based on the current state of the application.

    In other words, you need to set a flag in response to clicks on ID_FILE_32771 so that your program knows it should be calling LoadAndBlitBitmap2 when it repaints itself, instead of LoadAndBlitBitmap (why aren't they the same function, to which you simply pass different file names?).

    Outside of the WM_PAINT handler, you cause a paint event to occur by invalidating the contents of your window. The simplest and most generally appropriate way to do that is by calling the InvalidateRect function. For example:

    case ID_FILE_32771:
            // Set a flag indicating that picture2 should be painted
            // (or some other equivalent mechanism)
            paintPicture2 = true;
    
            // Signal that the window should be repainted.
            InvalidateRect(hWnd, NULL, TRUE);
    
            break;
    

    By calling UpdateWindow after InvalidateRect, you'll force an immediate repaint, but this is rarely necessary. Alternatively, you can skip the two-function-dance and call the more powerful RedrawWindow function directly.

    Either way, you'll invoke your WM_PAINT message handler, and it will query the application's current status and paint the appropriate picture.

    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
    
        // Paint the appropriate thing, depending on the app's current state
        if (paintPicture2)
            LoadAndBlitBitmap(__T("D://picture.bmp"), hdc);
        else
            LoadAndBlitBitmap(__T("D://picture.bmp"), hdc);
    
        EndPaint(hWnd, &ps);
        break;