10 Things I Hate About Win32

"Remove head from sphincter, then design an API."

I've come across a few problems with Win32 programming while implementing the vGUI library, and they quite often involved a lot of effort in tracking down the solution. Google helped with most of these problems, but they were often hard to find. Therefore the main purpose of this article is to collect this knowledge together in a hopefully Google friendly form.

For the record, Win32 is a reasonably good API for the most part, in my opinion, but some parts of it suck big time. The biggest source of problems seems to have been poorly designed common controls, which are then rolled out to millions of Windows users, so the poor developers have to cope with the badly behaving/buggy versions of these controls. Even newer controls that were introduced in Windows 95 have poor design - the tree control, for example, is a classic case in how not to design an API. Almost anything is possible (probably) with this control, but you just try to work out how to do it. It's much too complicated.

Anyway, here's the 10 Things I Hate About Win32:

Ok, so it's more than 10, but that's how many things I have to moan about!

Common Controls cache style flags

Keywords: SetWindowLong, SetWindowLongPtr, GWL_STYLE, GWL_EXSTYLE

This is possibly one of the nastiest problems with controls - mainly because there isn't an easy or comprehensive solution.

Basically, some controls will only look at their style flags when they are created. You can, of course, change the style flags at runtime with SetWindowLong(hWnd, GWL_STYLE) or similar, but it seems to be random as to whether each particular class of control will notice. Some controls allow you to change style flags at runtime, and will cope with it quite happily. Others will use the style flags to initialise themselves and then never look at them again. Still others will only respond to some style flags changing, and not others.

So you either have to find out the documented behaviour (good luck), or try it and see if it works. And then test it on all the various versions of the common controls, to see if there are any variations.

This is not a pleasant prospect, so I strongly recommend you do not change a control's style flags on the fly, unless it is clearly documented as a valid way to communicate with the control. Or unless you really need to.

Fortunately most controls will have some other way of doing what you want to do - usually via a control-specific message - but it's not always obvious from the documentation.

For example, the ListView control has a number of 'extended style' flags. You might be forgiven for thinking (as I did) that these are normal extended style flags. They're not. To use the extended style flags you need to send the LVM_SETEXTENDEDLISTVIEWSTYLE message to the ListView control. Calling SetWindowLong() with GWL_EXSTYLE will have no effect. I eventually worked this out when I noticed that the ListView 'extended styles' clashed with the standard Window 'extended styles', and deduced they must refer to two different sets of flags (hence they were given the same name...what a great idea).

You can't re-parent all controls

Keywords: SetParent, WM_COMMAND, change parent, controls, listbox, editbox, combobox

This one is even more annoying than the previous item, mainly because it's caused by such a stupid implementation.

Put simply, some of the common controls (EditBox, ListBox and ComboBox) will not allow you to move them to another window using the standard Win32 function, SetParent(). Or rather, they will, but they will continue to deliver their notification messages to the original parent. (This is because they cache the parent window handle - GetParent() is apparently too slow for them to use, for some reason.)

Therefore, avoid re-parenting these controls. I used to do this in vGUI to implement tab controls, but had to change to using a child dialog to avoid this particular problem.

See Microsoft Knowledge Base article Q104069 for details.

Note: The MSKB article suggests a workaround which is not particularly attractive, but may be of use if you really need to reparent controls.



Goddamn Combo boxes

(Not written yet)

ListView controls

(Not written yet)

Tab Controls

(Not written yet)

Modeless Dialogs and IsDialogMessage()

Keywords: IsDialogMessage(), WM_GETDLGCODE, keypress, dialog, WM_ACTIVATE

If you use any modeless dialogs, then you need to call IsDialogMessage() in your message handling loop. Sadly, Windows isn't clever enough to know what's going in your app (for reasons I have yet to fathom), so you have to tell it which dialog in your app currently has the focus.

The practical result of this is that you need to process WM_ACTIVATE messages to keep track of when modeless dialogs become active or are deactivated. The currently active modeless dialog handle is what you pass to IsDialogMessage() in your message loop.

The simplest way to do this is to have a global variable or a singleton object that keeps track of this dialog handle. You can leave the message unclaimed, so that you don't change the existing processing of these messages.

By way of example, here is the code from the generic DialogProc from my vGUI library. Some of this code is vGUI specific, but you'll get the general idea:


case WM_ACTIVATE:
{
  Win32_DialogImp* pWin32Dialog = GetImpFromHWND(hwndDlg);

  if (!pWin32Dialog->m_bModal)
  {
    // Modeless dialog - we need to keep the app up to
    // date with which modeless dialog is active, so it
    // can call IsDialogMessage() correctly.
    //
    // Gorgeous...

    if (wParam == 0)
    {
      // becoming inactive
      GetWin32App()->SetModelessDialog(NULL);
    }
    else
    {
      // becoming active
      GetWin32App()->SetModelessDialog(hwndDlg);
    }
  }

  // We don't need to claim the message
  break;
}

Then, in your message loop, you just need to check for an active modeless dialog box before calling TranslateMessage() and DispatchMessage(), for example:


while (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
  // Check for dialog messages first...
  if ((m_CurrentModelessDlg == NULL) ||
       !::IsDialogMessage(m_CurrentModelessDlg, &msg))
  {
    // Not a dialog message, so process normally
  }
  else
  {
    // Dialog message - it has already been processed fully
    // by IsDialogMessage(), so no further action required.
  }

  // etc...

Once you have this mechanism in place, a lot of the dialog's keyboard handling routines will work much better (i.e. they will actually work).

WM_ENTERIDLE doesn't work

Keywords: WM_ENTERIDLE, dialog, hang, freeze, infinite loop

Well, it might work, but I've never been able to get it to work using the Microsoft documentation.

The following code from vGUI offers the clearest explanation of what I've tried and what happens. (This code is disabled in vGUI at the moment, because it hangs the program.)


    // NB. This code is disabled, because despite what the MS
    // docs say, it doesn't work. If this code is enabled,
    // the loop never exits because PeekMessage() never reports
    // that there are any more messages to process, despite me
    // moving the mouse like crazy. Go figure.
    case WM_ENTERIDLE:
    {
      if (wParam == MSGF_DIALOGBOX)
      {
        // Modal dialog is entering idle loop - service
        // our idle handlers until we get another message
        // in the queue
        MSG Msg;

        do
        {
          GetApp()->ServiceIdleHandlers();
        }
        while (::PeekMessage(&Msg, NULL, 0, 0, PM_NOREMOVE) == 0);
      }

      // Message processed
      return TRUE;
    }


DrawFocusRect() doesn't

Keywords: DrawFocusRect, wrong colour, EOR

This is quite a simple one, but vexing nevertheless. The DrawFocusRect() function will draw the EOR'd dotted rectangle around an item for you, of the type you will see in listboxes, and so on.

This is very useful if you are doing owner-draw listboxes/listviews. However, DrawFocusRect() seems to assumes certain things about the DC you ask it to draw into. If you've changed the DC's colour or other attributes, it seems that DrawFocusRect() will go wrong, as it doesn't bother to set the correct colour. This is presumably due to one of two explanations:

  • It allows you to draw focus rects in whatever colour you like.
  • It's a bug.

Either way, there's usually no good reason to make your control use a different colour focus rectangle to all the other controls in Windows, so you usually want the standard colour to be used. The simplest way to do this is to call SaveDC() when you are passed the HDC to draw with in, say, your owner-draw listbox, and then call RestoreDC() before calling DrawFocusRect().

This ensures any changes you make to the DC do not affect the rendering done by DrawFocusRect().

Here's some source code as an example, taken from the vGUI library. It is used to draw items in a 'listbox' control:



    //
    // Ok, we have the text colour, so let's render the text.
    //
    // NB. If we don't use SaveDC/RestoreDC here, then DrawFocusRect
    // does not render correctly.
    int iDCState = ::SaveDC(pDIS->hDC);

       ::SetTextColor(pDIS->hDC, TextColour);
       ::SetBkMode(pDIS->hDC, TRANSPARENT);

       ::TextOut(pDIS->hDC,
                 pDIS->rcItem.left, pDIS->rcItem.top,
                 ItemText.c_str(),
                 ItemText.GetLength());

    ::RestoreDC(pDIS->hDC, iDCState);

    // Draw focus rect if necessary (do this last as
    // it uses XOR drawing)
    if (pDIS->itemState & ODS_FOCUS)
       ::DrawFocusRect(pDIS->hDC, &pDIS->rcItem);


Icons? Any size you like, Sir, as long as it's 32x32

Keywords: LoadIcon, DrawIcon, icon, wrong size, scaled

Another really annoying one - basically, if you're doing anything with icons in Win32, be aware that many of the Win32 functions do not understand any icon size other than 32x32.

A good example of this is LoadIcon() - if you use it to load an icon that is, say, 16x16, it will scale it up and create a 32x32 icon for you. There are quite a few other examples where icons are assumed to be 32x32 - this is a backwards compatibility problem, I assume.

If you want to load a non-standard size icon (i.e. other than 32x32) then use LoadImage() with the IMAGE_ICON type code.

A reasonable rule of thumb seems to be that Win32 functions with 'Icon' in the name may only handle standard icons. Look for a newer function - for example, DrawIcon() only draws standard sized icons - you need to use DrawIconEx() instead.

Note: The 32x32 size comes from, I believe, two system metrics: SM_CXICON and SM_CYICON. If you have 'large fonts' enabled in Windows, the size may well be different.



Nested dialogs

Keywords: WM_GETDLGCODE, GetNextDlgTabItem, infinite loop, hang, crash

Although you can nest one Win32 dialog as a child of another, you should be very wary of using more nesting levels than that - including making a dialog a child of a control in the parent dialog. Due to a bug in the Windows tab order logic, you can put your application into an infinite loop without realising it, and it will take you ages to work out why.

For example, in vGUI, my original code for implementing tab controls created a child dialog for each tab, and attached them as children of the tab control. This meant that (logically, and in theory) tab ordering/Z-ordering would always be correct.

Sadly, when you clicked in certain controls on the tabs, the program would go into an infinite loop, because Windows would repeatedly send it WM_GETDLGCODE messages. The program was responding correctly, but Windows would just send another message, and another, and so on.

The bug is explained somewhat in Microsoft Knowledge Base Article Q149501, which is about property sheets that exhibit similar problems.

The workaround contained in that article did not work in the general case so I fixed my code to make the child dialogs be children of the main dialog, instead of being children of the tab control. This works fine - as long as you ensure the position the dialogs correctly in the Z-order (i.e. above the tab control in Z-order). Before you try that, read the item on confusing Z-ordering to save yourself some grey hairs and frustration.

Returning result codes in dialogs

Keywords: DialogProc, DWL_MSGRESULT, dialog message not processed

I've put this one in here because it's an easy one to forget. A DialogProc returns either TRUE (to indicate that it processed the message) or FALSE (to indicate that it did not).

However, some messages need more than just a handled/did not handle response - but don't just try to return this from the DialogProc (as you would from a WindowProc), because it won't work.

What you need to do is this, assuming that lResult contains the value you want to return to the message sender:

    SetWindowLong(hwndDlg, DWL_MSGRESULT, lResult);

This one is fairly clearly documented, but it is very easy to forget now and then, which is why it's in my list.

It's especially easy to make this mistake because Windows uses a non-standard boolean type, BOOL, which is defined as an int to return values from a DialogProc, and the LRESULT value is an unsigned long, and is used to return values from a WindowProc. Therefore the compiler will silently convert between the two for you, if you erroneously return an LRESULT (which is usually just a numeric literal via a #define constant) from a DialogProc.

Confusing Z ordering

Keywords: SetWindowPos, GetWindow, GW_HWNDNEXT, GW_HWNDPREV, hWndInsertAfter, Z order

Like the previous item, this is a clarification issue. The documentation is not wrong - it's just a bit obtuse in places.

Windows stores Z-order information in a counter-intuitive way. At least to me, with my background of graphics applications, items in a Z-ordered list are stored lowest item first, highest item last. Windows does not do this - in a list of sibling windows, the first window is the one at the top of the Z-order.

For example, here is a screen grab from Microsoft Spy++, looking at the dialog of one of my applications:

Note
The easiest way I have found to think about this is that when you look at a tree display of your window hierarchy in Spy++, then that is a correct spatial representation of the Z-order - i.e. in the screen grab to the left, the higher up a window is, the higher it is in the Z-order.

For example, window 000E0B7A is above the window 000D0B62 in the screenshot, which means it is also above window 000D0B62 in the Z-order. This is about the only scenario where this ordering makes sense.

This dialog contains a tab control, and two child dialogs that are used to display the controls on each tab (they're the last 3 items in the list). The dialogs need to be in front of the tab control - and they are. You can see from the picture that the tab control is last in the list, which means it is at the bottom of the Z-order. The two child dialogs are before the tab control in the list, which means they are above the tab control in the Z-order.

That is a bit confusing, but it gets really confusing with window relationships, as in calls to GetWindow(). To get the window above/below the window you are interested in, you use the constants GW_HWNDPREV and GW_HWNDNEXT. Using GW_HWNDPREV gets you the window before the specified window, which is actually above the specified window. Similarly, GW_HWNDNEXT gets you the next window, which is actually below the specified window.

The problem is compounded with the hWndInsertAfter parameter of the SetWindowPos() function. It is documented as follows:

    hWndInsertAfter

        Handle to the window to precede the positioned window in the Z order.

So when you call SetWindowPos(), your window will actually be positioned below or behind the window specified in hWndInsertAfter. Again, this seems to me to be counter-intuitive. Similarly, when Windows has to draw the windows using the painter's algorithm, it has to traverse the list in reverse order.

You may be thinking "What's the problem? It makes perfect sense!" - in which case, lucky old you. The rest of us have to try to cope with the slightly counter-intuitive terminology.