why not to send WM_PAINT manually
Asked Answered
M

4

7

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;
  }
}
Meeks answered 16/3, 2014 at 14:48 Comment(4)
Where did you read it? Whoever wrote that should have included an explanation. Hint: Read what BeginPaint does.Perfunctory
@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.Meeks
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.Tamarisk
If you never call InvalidateRect, then BeginPaint says "Oh, the window is still valid. There is no need to paint anything."Perfunctory
M
11

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.

Melmon answered 16/3, 2014 at 16:58 Comment(0)
E
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.

Epimorphosis answered 16/3, 2014 at 22:38 Comment(0)
P
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.

Panicle answered 16/3, 2014 at 17:21 Comment(4)
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.Kaila
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.Panicle
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.Kaila
@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.Trifacial
T
2

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.

Tedric answered 24/4, 2015 at 9:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.