What happens if I don't paint when I get a WM_PAINT message?


Suppose your window procedure doesn't paint when it gets a WM_PAINT message. What happens?

It depends on how you don't paint.

If you have an explicit handler for the WM_PAINT message that does nothing but return without painting, then the window manager will turn around and put a new WM_PAINT message in your queue. "And try harder this time." Remember that the rules for the WM_PAINT message are that the window manager will generate a WM_PAINT message for any window that has a dirty region. If you fail to remove the dirty region in your WM_PAINT message handler, well, then the rules state that you get another WM_PAINT message. (The most common way of clearing the dirty region is to call Begin­Paint, but there are other less common ways, like Validate­Rect or Redraw­Window with the RDW_VALIDATE flag.)

The other case is that you simply don't have a WM_PAINT handler and let the message fall through to Def­Window­Proc. In that case, Def­Window­Proc will do a blank paint for you. In other words, Def­Window­Proc contains the logical equivalent of

case WM_PAINT:
 {
  PAINTSTRUCT ps;
  if (BeginPaint(hwnd, &ps))
   EndPaint(hwnd, &ps);
 }
 return 0;

In the case where you pass the WM_PAINT to Def­Window­Proc, the dirty region is cleared because Def­Window­Proc will call Begin­Paint for you.

There are some quirks in the handling of the WM_PAINT message by the Def­Window­Proc function to handle various application compatibility cases, but the above is the basic idea.

To avoid tripping over the weird application compatibility cases, decide up front how you want to deal with WM_PAINT messages delivered to your window procedure.

  • Handle them completely by calling Begin­Paint and End­Paint, then returning 0. (Do not pass the message to Def­Window­Proc.)

  • Pass them all to Def­Window­Proc, and let it do the Begin­Paint and End­Paint.

Don't try playing fancy games like "Oh, I'm going to call Begin­Paint and End­Paint, but sometimes I'm also going to pass the message to Def­Window­Proc afterwards." Just pick one plan and stick to it. It's a lot simpler for everybody that way.

Comments (19)
  1. Amin Mohammadi says:

    always i wonder can we corrupt GDI and prevent it from paint client area?

  2. Joshua says:

    Well, I suppose you should get a DC for the desktop window and scribble on it.

  3. Xv8 says:

    I thought WM_PAINT was where you put all your (important, blocking) logic, not for painting anyway?

  4. Henke37 says:

    For the record, the BeginPaint function will send the WM_ERASEBKGND message that DefWindowProc handles by using the specified BG erasing brush for the window class (which may or may not actually do have any effect) to clear the area. But only if the region was marked for erasing while asking for invalidation. It isn't defined if the region is marked for erasing when it's "the system" that marks for invalidation.

    I hope that clears up any confusion of what happens to the visual result if you try and skimp on the processing of WM_PAINT messages.

  5. SI says:

    Slightly off topic: I assume this means the correct way of handling painting with an async background thread is to have the main thread call ::ValidateRect() and trigger the extra thread to run a render / update pass as needed, which then updates the screen using a cached copy of the invalid region + whatever has become invalid in the meanwhile?

  6. AsmGuru62 says:

    I am not sure if painting on a thread is a good idea.

    I always thought that a thread should just update the data (in file or memory) and then post the redraw (InvalidateRect/UpdateWindow), but main thread should actually draw in response to WM_PAINT.

  7. Douglas says:

    @SI

    I might be (read: probably am) misunderstanding you, and this may not be a good solution, but here's my two cents.

    Ideally, all the information needed to paint the screen is obtained from some source (pointer to a struct?) that can be updated atomically. The main/GUI/event thread just grabs the info (with an acquire/consume barrier) from that whenever it needs to paint. If the data hasn't changed since the last update, it's not stale, if it has, it shouldn't be for long. When the background thread(s) has updated the data, it (with a release barrier) tells the main thread in some way (e.g. PostMessage a MY_UPDATE_MSG (I don't know how PostMessage interacts with memory ordering)) to update.

    Obviously, you'll need a way to handle disposing of the old data, and there're probably many other problems, but that's the way that occured to me. (And yes, I have done something like this in Java/Swing. SwingWorker is unbelievably helpful.)

  8. doynax says:

    SI: Seems a bit dodgy to me.. How about running a recursive message loop between BeginPaint/EndPaint (filtering with QS_PAINT/PM_QS_PAINT) while waiting for the rendering thread to signal completion?

  9. @Xv8: don't even mention that. We spent days tracking bugs in an old win32 applications written by someone who didn't have a clue about handling WM_PAINT & co. I have yet to see a wndproc of this guy which does things normally. I've seen all kind of antipatterns:

    - good old "update all the fundamental data structures of the program in WM_PAINT"; the window stays covered the whole time? Business-logic objects don't get created.

    - initializing data structures related to click handling in WM_PAINT; a WM_LBUTTONDOWN happens to arrive before the first WM_PAINT? Too bad, access violation.

    - initializing half of the data structures in WM_ERASEBACKGROUND, fixing up some others in WM_PAINT, and the rest in a WM_TIMER;

    - setting up an off-screen bitmap in WM_ERASEBACKGROUND, painting some of them in a timer, finishing in WM_PAINT and then doing a BitBlt; the first WM_TIMER arrives before WM_ERASEBACKGROUND? Access violation. Someone does InvalidateRect with last parameter FALSE? Access violation.

    - marking the window as "to repaint" in some cases by killing (delete/set to NULL) said bitmap, in other cases by setting a flag in the same data structure; someone tries to set this flag between the delete and the WM_ERASEBACKGROUND? Access violation.

    There are surely some others which I'm missing, but you got the idea.

  10. Azarien says:

    Interesting. I always thought it is EndPaint that validates the window. That would make more sense ;-)

    And that WM_ERASEBKGND is a nuisance. More often than not, I do

      case WM_ERASEBKGND: return 0;

    or its moral equivalent in WinForms.

    [If EndPaint validated the window, then you would have a problem if the window was invalidated while you were painting it. -Raymond]
  11. I'd really really like to see that DefWindowProc. It must be some kind of library spanning monster.

  12. Darran Rowe says:

    For new desktop applications, I can imagine that BeginPaint is going to become less common. Direct2D was released with Windows 7, and Windows 8 has DirectComposition. So as time goes on and skill sets improve, I can really see the use of GDI in new applications vanish almost completely.

  13. SI says:

    Douglas: Even in the cases where I have all the information cached locally (so no stale caches), the shear amount of information means a paint cycle may take several seconds.

    Doynax: The entire point of the background thread is to avoid blocking the main thread since the painting may take a while as it gathers data via COM. If the main thread waits / runs a loop could just lead to blocking reentrancy problems.

    I could have an extra paint buffer just so the main thread can update the window from the stale state and bitblt the new state to this buffer whenever it is done, but that just introduces another layer of caching and I have never seen anything that says using getDC / paint / releaseDC from an extra thread is wrong. But I also have never found a good example of a control with the requirements I have (so rendering via dedicated thread).

  14. SI says:

    PS: I am not trying to say my approach is correct, if I knew that I would not be asking, I am just trying to explain why I am doing it this way until I find a better way.

  15. Neil says:

    @Douglas: The best way to tell the main thread that it needs to paint the window is to invalidate it.

  16. doynax says:

    SI: The idea was to keep running the application as usual, just ignoring further WM_PAINT messages until the rendering is done. Not to go into a special modal state. Admittedly I don't know how to selectively ignore WM_PAINT for a single window if you still need to keep updating the rest of the user interface in the meantime. Surely there must be a proper way to defer the painting?

    AsmGuru62: Sounds reasonable but what do you do in WM_PAINT while the buffer is out-of-date, say after window sizing or because new input has arrived?

  17. SI says:

    ValidateRect seems like the easiest way to defer painting, and then InvalidateRect when a backbuffer is ready.

  18. doynax says:

    SI: Indeed and I imagine it works just fine for the most part. The fact that you're effectively lying to the API seems like it might come back to bite you in corner cases though, say if the window happens to be captured in the interim (screenshot, alt+tab icon, screen reader, etc).

  19. SI says:

    That's a good point and probably true for when the invalid state is due to the window size changing, etc. 99% of all invalidates are due to mouse interaction and animations, and then the last state is still about as valid as you can get without infinite cpu resources at your disposal until the new state is drawn.

    I did have to sync the two threads to handle WM_PRINTCLIENT, but since that message isn't autogenerated during COM message loops it doesn't lead to any problems.

Comments are closed.

Skip to main content