fixing scrollbars

what the hell, microsoft?

Retrocomputing projects are so last-year. After all, why else has Microsoft spent untold billions on ensuring backwards "compatibility", if not to personally entertain my every whim? Why shouldn't the same code work just as well on Windows 11 as on Windows 95?

It turns out that there is, unfortunately, one big reason - any app using default Win32 controls will look utterly atrocious on a modern machine. Worst of all, those nice thick scrollbars that we love so much will be replaced by the ADA-lawsuit-worthy scrollbars that Microsoft added as part of yet another pathetic attempt to cheaply copy MacOS.

A Windows 11 demo application, showing a barely-visible scrollbar
At 100% scaling, Windows 11 reserves a comfortable 16 pixels just like 9x does for its classic scrollbars, only to draw a line just 2 pixels wide in a color that barely shows up against its background.
Since these are obviously unusable for any practical purpose, we need a replacement that is:
  • As accessible as possible
  • Able to support customizable appearance
  • Ideally, a native control on old Windows

Most (if not all) native controls meet these criteria by default, except for standard scrollbars, which do not allow for any visual customizability. Without that, we're stuck with these horrendous default scrollbars and entirely unable to implement any custom themes. So, with these requirements in mind and a copy of the Oct. 2001 MSDN Library CDs in hand, it's time to find a scrollbar we can customize!

scrollbar contenders

attempt 1: overridden NC_PAINT

Standard scrollbars differ from most (if not all) other controls in having no HWND of their own, being merely "decorations" painted by DefWindowProc in the non-client area. Overriding WM_NCPAINT should therefore affect how scrollbars are drawn, and a quick test on Windows 2000 shows that "handling" it by returning 0 does indeed remove the scrollbars (and window frame, of course)! Let's just run this same binary on something more modern, and...

Screenshot of an application window displaying normally.
Vista's Classic theme with default WM_NCPAINT. Beautiful.
Screemshot of an application window with a broken titlebar and scrollbar.
Vista's Classic theme with overridden WM_NCPAINT. Note how the scrollbar region is free for us to paint over.
Screenshot of an application window displaying normally.
Vista's Aero theme with default WM_NCPAINT. Nice.
Screenshot of an application window with a broken titlebar, but working scrollbar.
Vista's Aero theme with overridden WM_NCPAINT. Note the missing titlebar, but the very real scrollbar.

Nope. Only the window frame goes away. It seems that Microsoft, in their infinite wisdom, has decided that the standard scrollbars are not to be trifled with anymore. That the Classic theme is unaffected tells me that the DWM compositor, in addition to being responsible for Vista's then-new Aero theme and transparency effects, is also responsible for this ridiculous behavior.

While it makes sense that scrollbars are broken by DWM commandeering the non-client area for its "glass" frame effect, it's not entirely clear why programmers aren't allowed to override this behavior. It's by no means surprising, however, since Microsoft's desire to force inflexible and unusable proprietary slop upon developers far outweighs their desire to ever ship a usable operating system at any point during the 21st century.

attempt 2: flat scrollbars

The old MSDN archive tells me about this thing called flat scrollbars, and raves about their customizability. However, a quick online search tells me the following:

"Flat scroll bars are supported by Comctl32.dll versions 4.71 through 5.82. Comctl32 versions 6.00 and later do not support flat scroll bars."1

Well, that's pathetic. Those versions first shipped with IE4 (1997) and XP (2001) respectively, it seems that flat scrollbars are too new to support 95 RTM, but were deprecated by XP, so we need another option.

attempt 3: scrollbar controls

As far back as I can tell, Windows has supported something called "scrollbar controls", a confusingly-named HWND-based scrollbar created to allow more than one scrollbar on a given axis. They serve well as a mostly drop-in replacement for standard scrollbars, meeting our criteria of accessibility and native theming.

A demo program in Windows 11, containing scrollbars reminiscent of Windows 9x.
A scrollbar control. Windows 11's botched implementation of the classic theme might be ugly, but at least it's more functional than the modern theme.

If you don't declare compatibility with comctl32 v6, Windows actually uses the classic theme to display these scrollbars by default. This is unfortunately rather useless, since the classic theme parameters require registry editing to customize in Windows 8 and later.

While a custom display routine can be trivially added through subclassing2, this still leaves us with a scrollbar that causes flickering on older systems and doesn't synchronize with the user settings in Control Panel. The second is somewhat simple to solve - handle WM_SETTINGCHANGE, which Windows sends to all top-level windows when the user has changed any system metrics.

Flickering, however, is a bit more complicated. It can either be caused by the parent and scrollbar attempting to paint in the same client area, or by GDI "erasing" the previous scrollbar by painting over the parent's entire client region with the background color. These problems would ideally be fixed by optimizing the parent's paint function, although WS_CLIPCHILDREN and ExcludeClipRect can also help in certain circumstances.

customizability at last!

Now, it's finally time to paint our own scrollbars!

Of course, nothing can ever be that simple. Microsoft, despite their countless remarks and insistence that controls do not paint outside of WM_PAINT, decided that scrollbar controls are somehow exempt from following their own damn rules! Because of this, every mouse click or scroll will override any custom painting and completely negate all our efforts.

As of now, the only solution I can find is to use WM_SETREDRAW to prevent the system from generating any paint events, forcing us to manually call UpdateWindow whenever we make a change. Unfortunately, this only works properly on systems with a DWM compositor enabled and also breaks the WS_CLIPCHILDREN and likely other things I haven't discovered yet.

Since DWM has been mandated since windows 8, and every previous version allows just using the classic theme anyway, this isn't a terrible compromise - users running on old Windows versions likely want the Classic theme, and not whatever my theming engine can provide. Still, I'd appreciate a more technically sound alternative if anyone can find it.

With all that out of the way, we can finally write our own WM_PAINT handler, and...

A demo program in Windows 11, containing custom-themed scrollbars.
A scrollbar control with custom theming. Notice how much of an improvement the contrast is - I can actually see where the handle begins and ends at a glance!

Here we are - thirty years of windows, one single customizable scrollbar! To truly take advantage of this, however, you'll need to slightly patch MinGW and to know how high-DPI APIs work in Win32 land. I'll be covering those in future articles, so stay tuned!

footnotes

  1. ^ https://learn.microsoft.com/en-us/windows/win32/controls/flat-scroll-bars
  2. ^ In win32 before comctl v6, subclassing is as simple as overwriting the stored address of the window procedure. To do this, call GetWindowLongPtr(hwnd, GWLP_WNDPROC) to get the current window procedure address, and then store it somewhere for later use. Then, write a window procedure that uses CallWindowProc with the stored window procedure as a fallback for all non-overridden events, and use SetWindowLongPtr to assign it to the scrollbar.