Building a flicker free volume control

When we shipped Windows Vista, one of the really annoying UI annoyances with the volume control was that whenever you resized it, it would flicker. 

To be more specific, the right side of the control would flicker – the rest didn’t flicker (which was rather strange).


Between the Win7 PDC release (what we called M3 internally) and the Win7 Beta, I decided to bit the bullet and see if I could fix the flicker.  It seems like I tried everything to make the flickering go away but I wasn’t able to do it until I ran into the WM_PRINTCLIENT message which allowed me to direct all of the internal controls on the window to paint themselves.

Basically on a paint call, I’d take the paint DC and send a WM_PRINTCLIENT message to each of the controls in sndvol asking them each to paint themselves to the new DC.  This worked almost perfectly – I was finally able to build a flicker free version of the UI.  The UI wasn’t perfect (for instance the animations that faded in the “flat buttons” didn’t fire) but the UI worked just fine and looked great so I was happy that’ I’d finally nailed the problem.  That happiness lasted until I got a bug report in that I simply couldn’t figure out.  It seems that if you launched the volume mixer, set the focus to another application then selected the volume mixer’s title bar and moved the mixer, there were a ton of drawing artifacts left on the screen.

I dug into it a bunch and was stumped.  It appeared that the clipping rectangle sent in the WM_PAINT message to the top level message didn’t include the entire window, thus portions of the window weren’t erased.  I worked on this for a couple of days trying to figure out what was going wrong and I finally asked for help on one of our internal mailing lists.

The first response I got was that I shouldn’t use WM_PRINTCLIENT because it was going to cause me difficulty.  I’d already come to that conclusion – by trying to control every aspect of the drawing experience for my app, I was essentially working against the window manager – that’s why the repaint problem was happening.  By calling WM_PRINTCLIENT I was essentially putting a band-aid on the real problem but I hadn’t solved the real problem, all I’d done is to hide it.


So I had to go back to the drawing board.  Eventually (with the help of one of the developers on the User team) I finally tracked down the original root cause of the problem and it turns out that the root cause was somewhere totally unexpected.

Consider the volume UI:


The UI is composed of two major areas: The “Devices” group and the “Applications” group.  There’s a group box control wrapped around the two areas.

Now lets look at the group box control.  For reasons that are buried deep in the early history of Windows, a group box is actually a form of the “button” control.  If you look at the window styles for a button in SpyXX, you’ll see:



Notice the CS_VREDRAW and CS_HREDRAW window class styles.  The MSDN documentation for class styles says:

CS_HREDRAW – Redraws the entire window if a movement or size adjustment changes the width of the client area.
CS_VREDRAW – Redraws the entire window if a movement or size adjustment changes the height of the client area.

In other words every window class with the CS_HREDRAW or CS_VREDRAW style will always be fully repainted whenever the window is resized (including all the controls inside the window).  And ALL buttons have these styles.  That means that whenever you resize any buttons, they’re going to flicker, and so will all of the content that lives below the button.  For most buttons this isn’t a big deal but for group boxes it can be a big issue because group boxes contain other controls.

In the case of sndvol, when you resize the volume control, we resize the applications group box (because it’s visually pinned to the right side of the dialog).  Which causes the group box and all of its contained controls to repaint and thus flicker like crazy.  The only way to fix this is to remove the CS_HREDRAW and CS_VREDRAW buttons from the window style for the control.

The good news is that once I’d identified the root cause, the solution to my problem was relatively simple.  I needed to build my own custom version of the group box which handled its own painting and didn’t have the CS_HREDRAW and CS_VREDRAW class.  Fortunately it’s really easy to draw a group box – if themes are enabled a group box can be drawn with DrawThemeBackground API with the BP_GROUPBOX part and if theming is disabled, you can use the DrawEdge API to draw the group box.  Once I added the new control that and dealt with a number of other clean-up issues (making sure that the right portions of the window were invalidated when the window was resized for example), making sure that my top level control had the WS_CLIPCHILDREN style and that each of the sub windows had the WS_CLIPSIBLINGS style I had a version of sndvol that was flicker free AND which let the window manager handle all the drawing complexity.  There are still some minor visual gotchas in the UI (for example, if you resize the window using the left edge the right side of the group box “shudders” a bit – this is apparently an artifact that’s outside my control – other apps have similar issues when resized on the left edge) but they’re acceptable.

As an added bonus, now that I was no longer painting everything manually, the fade-in animations on the flat buttons started working again!


PS: While I was writing this post, I ran into this tutorial on building flicker free applications, I wish I’d run into it while I was trying to deal with the flickering problem because it nicely lays out how to solve the problem.

Comments (30)

  1. Billy says:

    Nice post.

    I would love to read the explanation why group boxes are buttons though.

  2. Billy: If I knew, I’d have included it in the post.

  3. Mike says:

    This post highlights why writing Windows applications can be frustrating for us mere mortals. Try doing that all over again WITHOUT access to "internal mailing lists" and "one of the developers from on the User team." You’re only allowed to use newsgroups, blog entries, and the MSDN documentation. I’m timing you… go!

  4. jon says:

    I wish I could get back all the days of my life I’ve spent trying to get Windows common controls to be flicker free. It’s unbelievable how hard it is :(

  5. Matthew says:

    That last (unfixed) problem you mentioned — resizing the left edge of the window causing the right side to flicker — is driving me nuts.  

    I’ve run into it in any number of apps I’ve written and I’ve never successfully found a fix to it.  It appears that when you resize the left edge the window manager moves the whole contents of the window over to the left and only then gives you a chance to redraw.  

    If someone here knows a fix for this, I would be *incredibly* appreciative.  At least buy you a beer the next time you’re in town appreciative. :)

  6. steveg says:

    Did you try to subclass the group box and override its WM_ERASEBKGRND message in an attempt to retain native Groupbox drawing?

    My theory on BUTTON and BS_GROUPBOX is probably the obvious one: it seemed like a good idea at the time. It would have been less hassle to shoe-horn it into BUTTON rather than create a new window class. And possibly more importantly a couple less 16 bit handles consumed. I’d call it a design feature rather than a bug.

    It’s often hard to explain to non-programmers "yes, that bug fix took 5 days. And no, I’m not just out of university."

  7. steveg: That was one of my early ideas, but subclassing the group box retains the CS_VREDRAW and CS_HREDRAW class styles.  I had to built my own subclassed control and try it.

    Matthew: I don’t know how to fix it unfortunately and neither did the window manager developer who was helping me.

    Mike: I know how hard it is.  I only fall back on the internal DLs when I run out of ideas. Having said that, the information *is* out there (if I’d seen the link I included in this post when I was doing this, I’d have saved about 2 weeks of time).

  8. steveg: Btw, this bug collectively took about 4 weeks.  2 weeks for the first implementation and 2 weeks for the 2nd implementation.  It turned out that there were a huge number of things wrong that led to the flickering, however this issue was the only one that was blogworthy (for instance at one point the volume sliders were child controls of the group box which is a hideously bad idea and confuses the heck out of the window manager)

  9. Billy O'Neal says:

    Mike: I don’t believe I see where Larry used "Internal mailing lists" in the process of running down his issue.

    LarryOsterman: Thanks for the interesting article :) Found a typo for ya:

    "I decided to bit the bullet" -> "I decided to bite the bullet"

    Hope that helps :)

  10. Interesting problem and solution. However, you still mised something (or maybe someone else did): When I move the mouse over the "Speakers" drop-down list it fades in—but not completely, the left and right sides of the faded-in list are missing until it’s completely opaque and then "pop" into view. Similarly for fading out: The left and right sides (each around 5 pixels wide) stay visible until the rest has completely faded.

    Interestingly, they tend to fade in a little when doing a screenshot, which makes this pretty hard to capture but you can see the opacity differences:

    Also I notice that the D in "Devices" gets underlined a second time when I press Alt.

  11. Johannes: I didn’t miss it.  It’s an artifact of how the fade in logic works in the toolbar control – there isn’t a multiline drop down toolbar split button conrol so I had to build my own using custom draw messages (which was really easy to do).  Unfortunately the custom draw logic doesn’t handle the fade in so there’s a period of about .5 second where the styling for the top and bottom margins of the toolbar button style are incorrect.

  12. jon says:

    Rather than a sub-class, you could always super-class the button class and remove the redraw styles. Might work…

  13. Brian Ensink says:

    The flicker free drawing link you added in the postscript is a good description of double buffering.  One idea I’ve learned from others is to make a reusable class that encapsulates the memory device and bitmap and have the destructor of the class do the final BitBlt to the screen like shown here  I’ve used that class a number of times to double-buffer GDI draw code.

  14. Adrian says:

    I knew there had to be an underlying bug.

    The Windows painting model isn’t *totally* broken. 😉


  15. ATZ Man says:

    Notepad and pretty much anything that uses the edit control have needless flicker. I know it’s needless because I built a custom child window class that doesn’t flicker.

    But of course, *I* in my usage did not need the flicker and maybe at some point Larry, you could blog or get someone to blog on flicker in the edit control.

    BTW I didn’t remove the flicker by double-buffering the render.

  16. WndScks says:

    Larry, you should have taken the code from taskmanager, it already has a custom groupbox to deal with flicker: DavesFrameClass

    They question is, why not fix this in the common control? Add a new flag to the groupbox or something.

  17. James Brown says:

    Matthew: the left-side resizing issue can be fixed with clever usuage of the WM_NCCALCSIZE message.

  18. WndScks: I suspect that the reason they didn’t "fix" this in the groupbox implementation is that it would break applications.  Any time you make any changes to the common controls you run a huge risk of breaking things.

  19. Oh and until we realized that the problem was the CS_HREDRAW/CS_VREDRAW flags, we didn’t know that the problem was the groupbox.  We thought it was something unrelated.  

  20. James: You wouldn’t happen to have a sample, would you :)?

  21. James Brown says:

    Haven’t got a ‘good’ example. Here’s something though that kind of works. The scenario is, a top-level window calls this funciton (NcCalcSize) as it’s WM_NCCALCSIZE handler. This top-level window contains a child-window that has a right-side vertical scrollbar (that is SM_CXVSCROLL wide). And it has a status-bar at the bottom (g_hwndStatusbar). You can see there’s some ‘magic’ numbers in there too – can’t remember why they’re there – but probably just to fudge things to make it work.

    As you can see its messy and needs work. I had an email conversation way back with the author of – pretty sure he had it figured out.



    // WM_NCCALCSIZE handler for top-level windows. Prevents the

    //  flickering observed during a ‘top-left’ window resize by adjusting

    //  the source+dest BitBlt rectangles


    // hwnd – must have WS_CLIPCHILDREN turned OFF


    UINT NcCalcSize(HWND hwnd, WPARAM wParam, LPARAM lParam)


    ULONG ret = DefWindowProc(hwnd, WM_NCCALCSIZE, wParam, lParam);

    if(wParam == TRUE)



    RECT rc;

    int  sbheight = 0;

    extern HWND g_hwndStatusbar;

    GetWindowRect(g_hwndStatusbar, &rc);

    sbheight = g_fShowStatusbar ? : 0;

    nccsp->rgrc[1] = nccsp->rgrc[0];

    nccsp->rgrc[1].right  -= GetSystemMetrics(SM_CXVSCROLL) + 50;

    nccsp->rgrc[1].bottom -= GetSystemMetrics(SM_CYHSCROLL) + sbheight + 50;

    return WVR_VALIDRECTS;




    return ret;



  22. Alexandre Grigoriev says:


    Do you use BeginDeferWindowPos/EndDeferWindow pos? It helps a lot. If you also use WS_CLIPSIBLINGS for your dialog (which is not a default for dialogs) and make sure the group box is lower in Z-order, you could avoid the hassle of writing your own group box

  23. AnotherMike says:

    It’s really interesting to see that there’s a lot more "shuddering" when using the left window border compared to the right window border. The "shuddering" looks like a legacy issue that’s just as clearly visible in Windows 7 on a high end system as on an old system running a legacy Windows version … 😐

    How well do other OS’s/window managers handle this? (updating window content when the window is being resized)

    Are there any plans to reduce/fix this issue?

  24. jcs says:

    Larry: On my Windows Vista laptop, the mixer display has always been corrupted, ever since I first bought the machine. The latest patches are installed, but this doesn’t resolve the problem. Do you think my problem was also fixed in Windows 7?

    When I open the mixer, I see this:

    Resizing the mixer makes the corruption go away.

    It is also very hard to click on the "Mixer" link from the volume control applet on a tablet PC, because it is overly sensitive to any pen jitter between pressing and releasing the link. I do not have this problem on any other links in Windows — it seems that someone implemented their own click handling code, instead of using the standard Windows controls which are more tolerant of this jitter.

  25. jcs: That kind of problem screams "display driver bug" to me.  Are you sure you’ve got the most recent display drivers for your laptop?

  26. Roland says:

    Now I know why the tab control of Task Manager flickers so heavily when resizing the window. It has the CS_VREDRAW and CS_HREDRAW class styles set!

    (BTW, it’s really annoying that the Task Manager doesn’t use the LVS_EX_LABELTIP style for the Processes list view. Some columns such as "Description" can have long texts, and they get clipped easily. LVS_EX_LABELTIP would make them completely visible on hover.)

    Thanks for your hard work, Larry! Windows 7 is awesome!

  27. jcs says:

    I believe that I have the latest drivers that my OEM distributes. (neither Windows Update nor my laptop’s update utility offer me anything newer). However, it would not surprise me if this was a display driver bug, because this is a low-end laptop graphics chipset.


  28. Random832 says:

    I was searching for why a groupbox is a button, and instead found this question on stackoverflow which implies that you’re not really supposed to put controls inside a group box, but rather on top of it.

  29. Random832: Those are two different issues – Windows aren’t supposed to be children of buttons (the so issue) and why are groupboxes buttons.  It turns out that the Vista sndvol DID have the app group as a child of the groupbox but fixing it didn’t fix the painting problem