*Chime* “Some walk by niiiiiight…… Suuuhhm fly by daa-e-aay…”

Get it? Because Moonlighting also took a long time between new episodes due to blossoming movie careers and sexual tension? Oh, I think you know what I’m talking about.

Extremely previously I made a veiled threat to talk about the Panic Button Control Panel user interface. Today I make good on those careless whispers by taking a tour through what it takes to give the Panic Button UI its look, pausing along the way to refrain from drawing tenuously bleak conclusions about Windows programming which would betray my otherwise upbeat tone.

The Panic Button UI is a single window and that window is a just regular ol’ Windows dialog that’s been gussied up a bit. Below is the naked dialog in the Visual Studio designer alongside the run-time result. Everything in between is code and it’s that code we’ll be looking at. It was a lot of work but what’s the alternative, to skimp on the Gussie? I Fink-Nottle.

Most of the effect above comes from the background, so I’ll begin there. The entire background, including the white space, is just an image. This image is displayed with what the designer calls a Picture control but is really the all-purpose Static control with the SS_BITMAP style. I call it the cactus on the left. This is the simplest way to get a bitmap onto a dialog and normally wouldn’t be worth mentioning but there’s something here that breaks the textbook example: the image is really a GIF! There’s not a lot of programming environments left where using a GIF warrants an exclamation point, but there you go. Here’s the code that sets up the background, it’s called along with the rest of the program initialization upon receiving the WM_INITDIALOG message:

static void SetBackground(HWND hwnd)
{
    HWND hwndBackground = GetDlgItem(hwnd, IDC_BACKGROUND);
    RECT rectClient;
    GetClientRect(hwnd, &rectClient);
    MoveWindow(hwndBackground, 0, 0, rectClient.right, rectClient.bottom, FALSE);

    HBITMAP hBackgroundBitmap = CreateBitmapFromPicture(IDP_BACKGROUND, _T("GIF"));
    SendDlgItemMessage(hwnd, IDC_BACKGROUND, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hBackgroundBitmap);
}

First the background control is moved to the upper left corner and expanded to cover the entire dialog. This might seem like it should have been done in the designer, but it’s necessary to set the size at run-time because the image is set at run-time. Oh, and also Visual Studio will slit your throat if you try otherwise. The designer doesn’t allow you to set the size of Picture controls without images, which is inconvenient if you belong to the rare breed of ambitious that sets their sights beyond 22×21 pixeled cacti. So, that’s too bad, but the designer does relent if you set the control’s SS_CENTERIMAGE style, in which case you’re free to resize at will. That sounds okay in this case since centering an image within a control whose dimensions match is a no op, and though I’m not crazy about setting unnecessary properties for their unintuitive side effects, I’ve certainly done worse.

Here’s where that aforementioned throat puncturing comes in. Overlapping dialog controls render the designer unusable. Yikes. Mouse clicks cause the bottommost control to be selected, and since the background control is below every other control it would be impossible to select anything in the designer other than the background… which kinda defeats the purpose of a designer. This bug is the arch nemesis of Avery Lee, the Hero to millions who authored VirtualDub and the original bug report that Microsoft closed as Won’t Fix. As it still exists in Visual Studio 2010, someone has a 10 year birthday coming up…

So instead of dealing with all that I just size and position the background control at run-time with MoveWindow(). That way it doesn’t matter where I plant that stupid cactus as it ends up in the right place. The last detail to take care of is making sure that the background control is indeed the bottommost control on the dialog. The controls’ z-order is determined by the order they appear in the dialog resource, so all it takes is stepping out of the designer and directly editing the resource script file to place the background control first in the list.

Once the background control is in position and sized correctly it’s time to set the image. This is done with the STM_SETIMAGE message which requires a bitmap, but cantankerous old man that I am, I’ve shown up instead with a GIF embedded in the program’s resource section. Unsurprising to anyone unfortunate enough to have endured through Small Programs, I’ve done this for the typical old man reason of reducing program size. The raw bitmap of the background is 184KB versus the 38KB compressed GIF, and seems kinda silly to plunge into the madness of maintaining my own runtime library while letting a single image quadruple the program’s size. But bitmaps are the currency Windows deals in, so something is needed to bridge the gap. If I may be so bold as to direct your attention to CreateBitmapFromPicture():

HBITMAP CreateBitmapFromPicture(UINT resourceID, LPCTSTR type)
{
    // load picture
    IPicture *picture = LoadPicture(resourceID, type);
    if (picture == NULL) return NULL;

    // get bitmap handle
    HBITMAP hPicture = NULL;
    picture->get_Handle((OLE_HANDLE *)&hPicture);

    // copy picture to new bitmap
    HBITMAP hNewBitmap = (HBITMAP)CopyImage(hPicture, IMAGE_BITMAP, 0, 0, LR_COPYRETURNORG);

    // destroy picture
    picture->Release();

    // return new bitmap (must be destroyed by caller)
    return hNewBitmap;
}

No flash photography please. Despite the high comment-to-code ratio this function just raises more questions. I’ve got a GIF, I need a bitmap, now where in the wide wide world of gator can I find myself an LZW decompressorator? According to the pioneer spirit of Windows programming I’d need to find or build a compatible image library and compile it in with my program. On the other hand, according to Oregon Trail most pioneers were just meandering bankers that died of dysentery. Like… all of them. So it’s a little surprising that Windows itself has been providing programs with the ability to read GIFs and JPEGs as far back as Windows 95! These formats can be loaded into the IPicture interface through the very handy OleLoadPicture() and you get a drawable bitmap to go to town with. I suppose that’s surprising because decoding compressed image formats invented elsewhere is a fairly high-level operation for the low-level API landscape. Also, let’s face it, it’s a pretty damn useful feature. Or… OR…… maybe the real reason it’s so surprising is that nowhere in the IPicture or OleLoadPicture() documentation did anyone include the words “GIF” or “JPEG”. Ha! Why would they?

Armed with the understated IPicture it’s easy to see how CreateBitmapFromPicture() gets its bitmap. The GIF gets loaded into an IPicture which provides a bitmap handle to its fancy innards, but since I don’t want lug around an IPicture I just use that handle to copy the image to a regular bitmap which I return after killing off the IPicture. This function and the rest in Picture.cpp are all about simplifying and encapsulating IPicture so that I can easily use compressed images in the Bitmap Belt that is Windows. I find IPicture awkward to draw with directly, and loading an image into it is… well, judge for yourself. That first line of CreateBitmapFromPicture() hides a lot of work. Here’s LoadPicture(), the dude that slurps the GIF out of the resource section and into an IPicture:

IPicture * LoadPicture(UINT resourceID, LPCTSTR type)
{
    // get pointer to resource data
    HINSTANCE hInstance = GetModuleHandle(NULL);
    HRSRC hResource = FindResource(hInstance, MAKEINTRESOURCE(resourceID), type);
    DWORD resourceSize = SizeofResource(hInstance, hResource);
    void *resourceData = LockResource(LoadResource(hInstance, hResource));
    if (resourceSize == 0 || resourceData == NULL) return NULL;

    // copy resource data to memory that has a handle
    HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, resourceSize);
    if (hGlobal == NULL) return NULL;
    void *globalBuffer = GlobalLock(hGlobal);
    if (globalBuffer == NULL)
    {
        GlobalFree(hGlobal);
        return NULL;
    }
    CopyMemory(globalBuffer, resourceData, resourceSize);
    GlobalUnlock(hGlobal);

    // create stream from copied resource data, then load picture from stream
    IStream *stream = NULL;
    IPicture *picture = NULL;
    if (CreateStreamOnHGlobal(hGlobal, FALSE, &stream) == S_OK)
    {
        OleLoadPicture(stream, 0, FALSE, IID_IPicture, (void **)&picture);
        stream->Release();
    }

    // cleanup
    GlobalFree(hGlobal);
    return picture;
}

Try not to stare directly at it. There’s really a lot less going on than the number of functions involved would suggest, it’s just that there are a lot of rules regarding how to get between these particular points A and B. The resource we’re loading, in this case a GIF, is already sitting in memory and the goal is to get that memory to OleLoadPicture() so that it can create an IPicture out of it. Seems simple enough but first we have to get a pointer to that GIF memory, which for resources requires the Find/Size/Lock/Load dance that makes up the first code block. Now we have the memory address but it turns out that OleLoadPicture() doesn’t want a raw memory address, it wants memory packaged as an IStream. Fine. IStreams can be created from existing memory by CreateStreamOnHGlobal()… which also doesn’t want a raw memory address. Instead, as the name says, it wants an HGLOBAL handle to a block of memory. Oh, what’s one more layer? So… we allocate a chunk of movable memory with GlobalAlloc() to get our HGLOBAL, copy the GIF from its original address to the movable memory address retrieved by GlobalLock(), use the HGLOBAL to wrap the movable memory in an IStream interface, and finally hand that off to OleLoadPicture(). Like I said, just try not to stare directly at it. Utility functions that hide the paperwork like this are great… until you foolishly return years later to write about them.

Now that the background is taken care of it’s time to get the text looking propah. In picture-talk, me want this:

Seems like a reasonable request. I want to color the status text in the corner and make the background of the labels and the checkbox transparent so that the background image shows through instead of the dialog color. All of this can be done by handling the WM_CTLCOLORSTATIC message:

static HBRUSH OnCtlColorStatic(HWND hwnd, HDC hdc, HWND hwndChild, int type)
{
    // initialize the HDC to the control's defaults
    FORWARD_WM_CTLCOLORSTATIC(hwnd, hdc, hwndChild, DefWindowProc);

    switch (GetDlgCtrlID(hwndChild))
    {
        // if XP theming is on then the transparent trick doesn't work for
        //  checkboxes, they turn out solid black instead. nothing is easy.
        //  since the checkbox happens to be on a solid white background
        //  i can just take the easy way out and set its background to white.
        case IDC_STARTUP:
            SetBkColor(hdc, RGB(255, 255, 255));
            return GetStockBrush(WHITE_BRUSH);

        // set the appropriate text color for the button status
        case IDC_BUTTONSTATUS:
            COLORREF textColor = (_hPanicButton == NULL) ?
                                  RGB(255, 0, 0) :
                                  _isButtonPushed ? RGB(255, 128, 0) : RGB(0, 128, 0);

            SetTextColor(hdc, textColor);
            break;
    }

    // make the static controls have transparent backgrounds by
    //  not painting the text background or the control background.
    SetBkMode(hdc, TRANSPARENT);
    return GetStockBrush(NULL_BRUSH);
}

The WM_CTLCOLORSTATIC message is sent to the dialog when Windows needs to draw one of its static controls or the checkbox, and the dialog’s response determines the colors that get used. This has always been a confusing area for me. First, I have to keep reminding myself that a control’s color isn’t a property of the control, but rather the answer to a question occasionally asked of its owner. It’s not the hardest concept in the world, but it’s unintuitive and whenever I have to remake its acquaintance my face does some contorting as I slowly re-accept the fact. Second, there’s the issue of just how many colors we’re dealing with here. I’m simple folk, so when I want to set the colors of a simple control I only care about two things: the text color and the background color. Being simple folk, as I purnt near is, I don’t even consider a nuance such as the background color of the text on the control vs the background color of the entire control. I went a very long time before connecting those dots, so I’m going to take a quick detour into a sample that might help clear this up:

This is a blue window containing a single static control. The static’s background color is green, its text background color is red, and its text color is yellow. Here’s the WM_CTLCOLORSTATIC handler that produced this:

static HBRUSH greenBrush = CreateSolidBrush(RGB(0, 255, 0));

static HBRUSH OnCtlColorStatic(HWND hwnd, HDC hdc, HWND hwndChild, int type)
{
    SetTextColor(hdc, RGB(255, 255, 0));  // Yellow text
    SetBkColor(hdc, RGB(255, 0, 0));      // Red text background
    return greenBrush;                    // Green control background
}

Now that it’s clear how many colors are involved and how to set them, the Panic Button code starts to make sense. Setting the status text color is as simple as, well, setting its text color, and giving everybody a transparent background just means abstaining from drawing either of the background colors. The TRANSPARENT background mode eliminates the text background color, and the NULL_BRUSH takes care of the control background color. This works great for static controls and used to work for checkboxes and radio buttons… but the Visual Styles introduced in Windows XP broke that behavior, much to my sadness. When Visual Styles are enabled, checkboxes and radio buttons treat responses to WM_CTLCONTROLSTATIC a lot differently. As far as I can tell these exact differences aren’t documented but it doesn’t matter because the short version is that none of them are helpful. In this case, returning the NULL_BRUSH causes this:

Blerg. At this point the right move is to just give up and acknowledge that the checkbox happens to be placed over a patch of solid white and simply set the checkbox’s background to white. As you can see in the above code, that’s exactly what I did. Giving up sucks, but I have no idea how to get a transparent background on a modern checkbox anymore without building my own from the ground up. Oh wait, yes I do, by writing web applications instead. Dammit.

You may have noticed that the whole transparency issue could have been sidestepped by moving the “Run When Pushed:” text a few pixels to the left, giving this entire discussion an undercurrent of lunacy. You’re right! My original design for the dialog depended a lot more on transparency, and it was only after writing the code for it that the final design rolled around and made it unnecessary. Since I’d already taken the time to get transparency working I made sure there was still a tiny amount of overlap to show it off, even if it didn’t really matter. Oh what, like you’re so rational?

Irrationality is the perfect segway into the final piece of Panic Button UI hackery, a maddeningly tiny visual effect that was the hardest part of the whole damn program. So difficult, in fact, that it wasn’t until a year after the program was written that I got it added in. See for yourself:

And yes, segway. As you can see, or at least as I sure hope you can see, the browse button on the left doesn’t look like the rest of the buttons. They’re rocking the new Visual Style theme and it’s going for the old Windows 95 look. It turns out that giving a button an image immediately disqualifies it from being drawn with Visual Styles. Blerg I say, I added that image to make things look better, not worse.

This is about the time when Windows programmers chime in and suggest creating an owner drawn control, which is a polite way of telling you to Get Stuffed. I’ll save going into owner drawn buttons for another day, but if the goal is to make a control’s look and feel exactly match what Windows does elsewhere, then doing everything yourself is the least likely way to get there. The Panic Button program went a year with an ugly button because faced with the option of owner drawn I simply backed down. Also, at that point I was successfully using the Panic Button to rock out to Table Dance every five minutes so my plate was pretty full.

Fortunately, a Hero named Stephen C. Steel (awesome name, he should front a detective agency) solved this exact problem and just as fortuitously, I read about it. Stemmington (that’s his detective name now) pieced together that a compromise existed between living with ugly image buttons and going full pull with owner drawn buttons. If a button only has an image and no text then by cleverly handling NM_CUSTOMDRAW notifications sent through WM_NOTIFY you can have an image button with the fancy style! It’s a great compromise because the image drawing code is minimal and straight forward, you’re not on the hook for the responsibilities that owner drawing incurs, and when Visual Styles aren’t around the button gets drawn by Windows normally so it’s compatible with earlier versions. Man, Stephanie Zimbalist is going to have her hands full with this dude…

The only issue for me was that Steel’s code was caught up in the Microsoft Foundation Classes lifestyle, so I ported it into a set of simple Win32 functions and along the way fixed a minor bug or two. I packaged all of this up in ImageButtonXP.h/cpp and I think the only thing to show is how easy it is to drop in and use. Here’s the code that sets the button’s image and handles the NM_CUSTOMDRAW notifications:

static BOOL OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam)
{
    ...
    // set browse button's icon
    HICON hBrowseIcon = (HICON)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_BROWSE),
                                         IMAGE_ICON, 0, 0, LR_SHARED);
    SetButtonIcon(hwnd, IDC_BROWSEBUTTON, hBrowseIcon);
    ...
}

// handles WM_NOTIFYs of NM_CUSTOMDRAW so that my stupid little browse button
//  can have its stupid little image *and* be themed.
// handling WM_NOTIFY from within a dialog is a little goofy. normally this
//  function would return an LRESULT, but since this is a dialog the return
//  value is whether or not the message was handled. if it was, then the
//  LRESULT needs to be set with SetWindowLong(DWL_MSGRESULT).
static BOOL OnNotify(HWND hwnd, int idCtrl, LPNMHDR pnmh)
{
    if (pnmh->code == NM_CUSTOMDRAW && idCtrl == IDC_BROWSEBUTTON)
    {
        HWND hwndButton = GetDlgItem(hwnd, idCtrl);
        LONG result = HandleCustomDrawNotification(hwndButton, (LPNMCUSTOMDRAW)pnmh);
        SetWindowLong(hwnd, DWL_MSGRESULT, result);
        return TRUE; // message was handled
    }

    return FALSE; // message wasn't handled
}

Once you get past the WM_NOTIFY cruft you can see that all we’re doing is handing off NM_CUSTOMDRAW messages to HandleCustomDrawNotification(), where the ImageButtonXP magic happens, and returning the result. Purdy simple, and now when you see a themed image button you’ll know the score. Someone either did a ton of work or they borrowed a ton from someone else. Or they used .NET and moved on with their life.

So now you know what it takes to polish a desktop application to the level that can achieve a confirmed userbase of two. I wrote out “two” because it looks bigger than “2″, and this article is all about appearances.

This is the end of the tour. I hope it didn’t come off as a laundry list of birthing pains, the idea really is to be helpful. And the intent isn’t to complain, it’s to show the amount of effort and code involved in creating a laughably simple dialog box that has some color. I mean that literally, like colors other than gray.

I promised at the start to stay away from drawing conclusions from this walkabout, and I’ll try to stick to that. I will say that when I see a Windows program with dialog boxes that use images, colors, or basically anything that strikes me as interesting, intuitive or unique, I check out the language it’s written in and nine times out of ten Delphi is involved. I don’t know what that means, I’m not dealing with a particularly large sample, but it seems weird that you can so often guess a program’s language by how friendly or boring its interface is. I don’t know anything about Delphi or VCL, but if I had to guess I’d say they must be pretty easy to experiment with. My basic UI took work that couldn’t have been justified on a project with economic constraints. Admittedly, Panic Button is sitting pretty low on the abstraction pole. I was programming directly against the Windows API without third party libraries so things could only have gone up from there, but with the goals of low level device communication and lightweight portability there was really nowhere else to go. (56.5KB, if anyone was counting!)

I guess I do have a wild baseless conclusion, but I’ll keep it meek: Most Windows programs look boring because it takes a lot of code to try to look interesting with the Windows API, which means it takes a ton of code to succeed.

That’s the last we’ll hear about Panic Button. Oh wait, that’s not true, next time we’ll see a program you can run with it, but don’t worry because it will suck and there won’t be any pictures. I suppose if I have to stand trial in Hong Kong for product defamation you’d hear about that, or if I were to receive a windfall when Asia realizes that my program has been driving worldwide button sales since March, but these things could take a while. Until then, hold tight.

PanicButton.exe
PanicButton.zip (source code)