5

I have read that I should never send WM_PAINT manually and should call InvalidateRect instead but didn't found anything about why not, however. So why not?

update works with InvalidateRect but not with SendMessage(WM_PAINT)

LRESULT CALLBACK window_proc(HWND wnd, UINT msg, WPARAM w_param, LPARAM l_param)
{
  switch (msg)
  {
    case WM_PAINT:
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(wnd, &ps);

        Polyline(..);

        EndPaint(wnd, &ps);
        return 0;

    case WM_USER:           
        // SendMessage(wnd, WM_PAINT, NULL, NULL);
        // InvalidateRect(wnd, NULL, FALSE);

        return 0;
  }
}
Ivars
  • 2,375
  • 7
  • 22
  • 31
  • 3
    Where did you read it? Whoever wrote that should have included an explanation. Hint: Read what BeginPaint does. – Raymond Chen Mar 16 '14 at 14:53
  • @RaymondChen it was some time ago.. it was something about that WM_PAINT should be carried by system. today i tried to send WM_PAINT from WM_USER message and it failed. i mean no effect. i'll add my code. – Ivars Mar 16 '14 at 14:59
  • Getting the code wrong like this is the primary reason you should not send WM_PAINT yourself. Always favor InvalidateRect(), call UpdateWindow() after that if you need to the paint to be done immediately. Which should be quite rare. – Hans Passant Mar 16 '14 at 15:50
  • 4
    If you never call `InvalidateRect`, then `BeginPaint` says "Oh, the window is still valid. There is no need to paint anything." – Raymond Chen Mar 16 '14 at 18:55

4 Answers4

9

Official docs for WM_PAINT state that you shouldn't in the very first sentence of the remarks section. Seriously, that should be enough of a reason not to.

As for technical reasons why, I guess this is one of them, taken from BeginPaint remarks section:

The update region is set by the InvalidateRect or InvalidateRgn function and by the system after sizing, moving, creating, scrolling, or any other operation that affects the client area.

Thus BeginPaint might not work correctly if you send WM_PAINT manually.

There might be more reasons/surprises.

user2802841
  • 903
  • 7
  • 13
6

If you want to trigger an immediate repaint, the correct approach is to either:

  1. Use InvalidateRect() followed by UpdateWindow().

  2. Use RedrawWindow().

Those will trigger a new WM_PAINT message to be generated.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
2

You don't have any information about other program's windows uncovering your windows. Only the operating system has this information. So you don't really know all the time when or where your window needs to be repainted. WM_PAINT and BeginPaint provide this missing information.

ScottMcP-MVP
  • 10,337
  • 2
  • 15
  • 15
  • 1
    Many (most?) WM_PAINT handlers repaint their entire window without looking at the invalid area to optimize it, so this answer doesn't really explain anything. – Mark Ransom Mar 16 '14 at 20:50
  • Whether a program uses the invalid area or not, it still doesn't have any way to know when something has been uncovered by other programs and needs repainting. So one has to handle WM_PAINT and one has to call BeginPaint in the handler. – ScottMcP-MVP Mar 17 '14 at 02:28
  • 1
    Nobody is arguing that WM_PAINT needs to be handled or that BeginPaint needs to be called. The question is why sending a WM_PAINT to yourself doesn't work, even though it ends up in the same handler code. This answer does not address this vital question. – Mark Ransom Mar 17 '14 at 04:34
  • @MarkRansom All the controls from the common controls library only redraw the invalidated area. Since most applications rely on those, most applications also get this behaviour for free. Many *custom* WM_PAINT handlers might do this wrong, but those are comparatively rare. – Luaan Sep 05 '19 at 08:25
1

Because WM_PAINT is not a real message.

Think of it like each window has a structure storing an "invalid region", that is, "this part of the window on the screen is not up to date anymore and need to be repainted".

That invalid region is modified by the window manager itself (window is resized, uncovered, etc ), or by calls to InvalidateRect, ValidateRect, EndPaint and such.

Now, here is a crude mock-up of how GetMessage handles this :

... GetMessage(MSG* msg, ...)
{
  while(true) {
    if(ThereIsAnyMessageInTheMessageQueue()) {
      *msg = GetFirstMessageOfTheMessageQueue();
      return ...;
    } else if(TheInvalidRegionIsNotEmpty()) {
      *msg = CreateWMPaintMessage();
      return ...;
    } else {
      WaitUntilSomethingHappend();
    }
  }
}

tl;dr: WM_PAINT is meant to be received, not sent.

Nicolas Repiquet
  • 9,097
  • 2
  • 31
  • 53