What is the implementation of WM_PRINTCLIENT?

Via the suggestion box, Martin Filteau asks

Could you explain the implementation of WM_PRINTCLIENT?

It seems that even MS got it wrong in the LISTBOX control.

Try to AnimateWindow a window that as a LISTBOX control as a child. The LISTBOX displays correctly if it is NOT empty. However, if it is empty... nothing is drawn.

I got a similar problem when embedding an Internet Explorer control.



As I noted back in 2003, the implementation of WM_PRINTCLIENT is the same as that of WM_PAINT. In particular, the implementation of WM_PRINTCLIENT for an empty window is... to paint nothing. That's why the window is empty.

In other words, the listbox control is correct to draw nothing when it is empty. To draw the empty set, you draw nothing!

Let's make some changes to the scratch program to show that animating a window with an empty listbox as a child works just fine.

OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
    RECT rc;
    GetClientRect(hwnd, &rc);
    g_hwndChild = CreateWindow(
        TEXT("listbox"), NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP,
        0, 0,
       (rc.right - rc.left)/2, rc.bottom - rc.top,
       hwnd, (HMENU)1, g_hinst, 0);

    return TRUE;

    wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,
                   LPSTR lpCmdLine, int nShowCmd)
        // ShowWindow(hwnd, nShowCmd);
        AnimateWindow(hwnd, 2000, AW_VER_POSITIVE);

Just to emphasize that the white background in the listbox is really being drawn by the listbox's WM_ERASEBKGND handler and isn't just leftover pixels from its parent window, I've positioned the listbox so it covers only half of the parent window and set the parent window's background to the application workspace color. (If your application workspace color is the same as the window color, then set the parent background color to something else. I can't believe I had to write that.)

With that simple set-up, we ask AnimateWindow to show our window, and specify a custom animation time so it's easier to see that the color is correct throughout the entire animation. The listbox child window does appear correctly: All you get is the window background. Nothing is drawn. just like it's supposed to be.

(This particular entry falls into another category of frustrating comment: The comment that claims that something doesn't work when it does, and forces me to write a test program to prove it.)

Comments (26)
  1. John says:

    Are you missing something from the scratch program modifications?  On my machine (XP SP3) the only thing that gets drawn is the list control focus rect; the rest of the window contains the unerased background (i.e. my desktop).

  2. Csaboka says:

    Your WM_PRINTCLIENT link points to the article with the scratch program. Could you fix it, please?

  3. Csaboka says:

    Never mind, the link is correct, I just didn’t read the bottom of the linked article. That’s what I get for posting before thinking…

  4. John says:

    I found the problem: remove the WS_VISIBLE flag from the call to CreateWindow.

    [If you remove the WS_VISIBLE flag, then the child window won’t be visible, so obviously it won’t paint! -Raymond]
  5. John says:

    Doh, sorry; I was reading the documentation wrong.  Incidentally, the animation worked for the parent window.  Do you have to do something special to get the list control to process WM_PRINTCLIENT or something?  I can’t figure out how to get this sample to work.

  6. Dan says:

    The customer’s problem was probably that they had some custom drawing code to draw a background image or something, and it wasn’t being drawn by WM_PRINTCLIENT.

    Either they weren’t drawing it in WM_PAINT or WM_ERASEBACKGROUND but instead drawing it elsewhere (ew).

    Or they DO do their painting in the WM_PAINT event, but WM_PRINTCLIENT doesn’t raise the WM_PAINT or WM_ERASEBACKGROUND events (WM_PRINT never raises them, I know that for sure).  They would need to handle WM_PRINTCLIENT themselves and run their custom paint code there too.

    And yeah, I think WS_VISIBLE shouldn’t be in the sample code, since it causes a window to be created visible, when instead we want it to start hidden and have AnimateWindow show it.

    [The WS_VISIBLE definitely should be in there. Otherwise the child window won’t be visible! -Raymond]
  7. John Schroedl says:

    I think the problem is that the customer didn’t understand the difference between the client area of the control and the entire control window.

  8. John says:

    So could anyone get this test program to actually work?

    [Works fine as-is. Stop screwing with the styles. -Raymond]
  9. John says:

    Does this require Vista?  I have tried this on 3 XP SP3 machines and have identical results on each one.  I copied the scratch program and made your modifications verbatim.  I don’t get an animation; I don’t even get any painting at all.  In fact, even the non-client area of the window is missing.  The ONLY thing that is drawn is the focus rectangle of the list control.  If I click where the title bar is supposed to be, all of a sudden it shows up and I can drag the window around.  When I drag the window, its contents consist of whatever was displayed on the screen at that location prior to launching the program.  For instance, if I launch it from the desktop and drag it around, it is essentially like dragging around a screenshot of my desktop.  If I drag it off the edge of the screen and back again then it gets painted properly.  I am not very familiar with the UI portion of the Windows API, so I don’t really know the proper terminology.  Perhaps more relevant: AnimateWindow returns FALSE and GetLastError returns 0.  If I remove the WS_VISIBLE style from the list control then the animation runs correctly, though obviously it doesn’t paint the list control.  Does this program work for anybody else?  Am I crazy, or did you accidentally omit something from your modifications?

    [I ran it on an old XP SP2 machine I have lying around. I retyped this program just to make sure – still runs fine. -Raymond]
  10. Anon says:

    Could it be that this works differently depending on whether you have "slide open comboboxes" and other related animation settings enabled/disabled? (see System Properties, Advanced, Performance).

  11. John says:

    I figured it out.  It does not work if you are using the Windows Classic visual style; it runs fine under the Windows XP visual style.  The sample program also paints properly under Windows 2000, although there is no animation (AnimateWindow returns FALSE and GetLastError returns ERROR_MOD_NOT_FOUND).  The behavior under XP seems like a bug to me.  If it’s not going to be supported under the Windows Classic style then I would expect it to fail gracefully and just behave like a regular ShowWindow as Windows 2000 seems to do.

  12. Anon says:

    @John: Yes I figured it would be something like this. Another case of Raymond being holier than thou assuming others must be idiots.

    [If the problem is under-specified, I’m going to complete the specification in the most convenient manner possible. Heck, I even took the extra step of testing on Windows XP; I could’ve stopped at Windows Vista. -Raymond]
  13. John says:

    To be fair, all he ever said was that the program runs fine; as far as he knew he was correct (he even tested it himself on XP).  I guess neither of us thought about Visual Styles (apparently neither did the QA department), so all of his tests passed and all of mine failed (I am a holdover for the classic style).

  14. Alexander Grigoriev says:

    Another case of “runs fine on my computer” QA.

    [It may shock you, but this blog does not have a QA department. -Raymond]
  15. Anonymous Coward says:

    AnimateWindow shouldn’t care if the listbox is drawn with or without visual styles. All it should care about is that its drawing code is functioning properly. Which means of course that there must be a bug in the code that does the drawing for the classic listbox.

  16. John says:

    I could not find any documentation about this behavior of AnimateWindow, so I consider it to be a bug.  If it’s not going to be supported under Windows Classic visual style then I would expect it to fail gracefully and revert to the Windows 2000 behavior.  However, since Mainstream Support for Windows XP is going to end in two weeks I suspect nobody is going to care.

  17. Mark says:

    Anonymous Coward: yes, every other control I’ve tried works fine with AnimateWindow.

    It looks like Raymond’s exposed the only control that stops a parent window being animated.

  18. Alexander Grigoriev says:

    “Another case of “runs fine on my computer” QA.

    [It may shock you, but this blog does not have a QA department. -Raymond]”

    In the suggestion box, the reader was reporting a quirk in listbox behavior. Which R.C. was very quick to brush aside, like a piece of BS. “Works on my computer”.

    Ultimately, though, the readers found that this behavior only happens in certain theme configuration, and looks like a genuine bug.

    “Can’t repro it, thus it’s not a bug”. Thank you very much, RC, for such attitude. That’s what’s keeping Windows bugs unfixed for years.

    [Again, something that may shock you: I do not research issues on this blog as thoroughly as I do for work. Heck, I went above my normal level of research for this blog and actually tried the program on two systems! -Raymond]
  19. Mark says:

    The issue is that user32!TakeWindowSnapshot works by sending WM_PRINT with PRF_CHILDREN.  This doesn’t have a return value, so it checks that something was drawn by calling GetBoundsRect on the cumulative HDC.

    As http://msdn.microsoft.com/en-us/library/dd145216.aspx dictates, the parent window passes WM_PRINT to its children.  However, the listbox’s implementation of WM_PRINT clears the bounds rectangle, instead of extending it (either by forgetting DCB_ACCUMULATE or by specifying DCB_RESET).

    My guess is that the themed implementation adds extra decoration at the end of the function, enlarging the rectangle again.  So the bug is only exposed when the listbox is the last control enumerated and when themes are off.

  20. John says:

    Cool.  Adding a child button to the main window made it work right under the Windows Classic style on XP.  It ALSO made the animation run properly on Windows 2000 (SP4 RU1), so this bug (or at least aspects of it) go back at least that far.  I wonder what the behavior is on Windows 2000 RTM or Windows 98; maybe I will test them at home.

  21. Bryan says:

    The real issue is non-specificity with the original submission.  If it works fine in default case X and you’re running in customized case Y, but fail to mention that, then the communication issue is on your part.

    I don’t care for bug reports that say "X API returns errors when we use it" when they really mean to say "X API returns errors when we use it in manner A with permission set B on Operating System C".

  22. Ulric says:

    interesting thread! I’m glad John what his problem was.

    I get a lot of Vista UI bug reports I cannot repro, all related to Vista in Classic mode.  

    With my app which uses OpenGL, there are tons of problems, update regions, clipping, etc, when Aero is not on.  In time, I’ve learned to assume that the users are running in classic mode even if they don’t say so.  It took months before the habit set in! I often have to send mails to QA and support to test that case when they resolve bugs are not reproducible.

    I don’t know where the bugs that affect my apps are located, in the third party graphics driver or window manager.  I can’t recall what the graphic card maker said.  Sometime we can put a work around with an extra refresh,  sometime we can’t.

  23. Mark says:

    Alexandre: The information we needed was that it works on Raymond’s computer, to exclude the possibility of a typo.  And it *should* work, so the bug is not in Raymond’s code (as people were suggesting) but in Windows.  John was the first person to identify the bug.

  24. Mark says:

    Ulric: I had exactly the same thought when I saw John’s first reply here.  The GDI/User/TS/Themes architecture has become very dodgy, mostly because complex UI was traditionally left to books, not documentation.  People are better off porting everything to DWM/WPF, sadly.

  25. Worf says:

    I think the problem is, everyone thinks classic mode is what Windows has internally. The XP theming stuff would appear to be fancy bitmaps spashed where Windows would draw its traditional stuff, and the classic mode is running underneath.

    An assumption made more real when you stop the theme service and everything revert s to almost-classic mode (things are too tall, notably). So it wasn’t until something like this that shows Classic isn’t pervasive as we thought…

  26. MadQ1 says:

    I’m too lazy to check if it works with a listbox, but the documentation for WM_PAINT says that some common controls check the wParam parameter. If non-NULL, it’s assumed to be a valid HDC into which the control will be painted.

    I’m not sure what the asker™ was actually trying to accomplish, but this might give him better results.

Comments are closed.