.NET Combo Boxes

It occasionally strikes me as hilarious that in the 21st millenium with Windows Vista near on the horizon with all kinds of spangly UI likely to result, we’re not only still using some of the oldest Windows UI primitives every day, but encountering problems using them. You’d think that we might have got it all sorted out by now. But no, we choose to use the latest technologies (such as .NET) to give us cool and innovative ways of achieving the same thing. And as a result we get a whole stack of cool and innovative new problems. This is, one imagines, why professional software developers still get paid; rather than companies simply hiring a finite number of monkeys and putting them in front of devenv. (There is a school of thought that says that some companies did in fact switch to the Monkey Methodology (also known as the Complete Muppet Paradigm, or Extreme Chimping) a long time ago. But I digress.)

I had a point to make, and it was something like this: .NET controls do not behave as a relatively seasoned Windows developer would expect they should. Now I’m going to dive into how controls were manipulated in pre-.NET days: not just for larks, but to illustrate my point. So bear with me.

As a sneak preview, I had the following problem. I created a drop down combo box containing a list of items; so you could select an item, edit its value in the combo box text area; select another item, edit its value, and so on. I had a special item in the list called “New…” which, if selected, would allow you to add a new item – the text you type becomes the text of a new item. To do this I had to watch for the text being edited and update the combo box item’s text accordingly. If the user selected “New…” and started typing, I created a new item and then typed text affected that. This was working fine. But having added a few items in this fashion, selecting an item in the list was not putting its whole value into the combo box text area. Instead it was putting only one character of the text in there. I spent a stupid amount of time fiddling with this before realizing what the problem was.

Since we’re not writing code in Java, we know that the controls we’re working with by and large map directly onto the native Windows Controls we know and/or love (usually or). On Windows, an old-style control is essentially an ordinary window (created with CreateWindowEx) which happens to have certain behaviour implemented by its window procedure (WndProc), responding to and sending certain standard messages. Combo boxes, list boxes, edit controls etc. are still just stock implementations in this fashion. In the bad old days, a developer thus communicated with these controls entirely through his or her main message loop (WndProc), SendMessage and PostMessage. This is as cumbersome as it is tedious, since to set the text of an edit control (for instance), one would write (in C++ here):

SendMessage(hWndEditControl, WM_SETTEXT, 0, static_cast<LPARAM>(_T(“Hello, world…”));

Which ain’t pretty. But it does have one advantage, in that there’s no obfuscation going on here. You send a message to the control’s window. This ends up in the message queue for that window’s owning thread, marked for that window’s attention. The thread running that window usually has a message loop a la:

while ( GetMessage(&msg, 0) ) // ask Windows for messages, exitting on WM_QUIT
{
    TranslateMessage(msg); // translate accelerator keys
    DispatchMessage(msg); // send message to the right window
}

So GetMessage() sits idly until a message shows up for somewhere. Assuming it’s not an instruction to bail, that then gets a quick dash past keyboard accelerator translation and then hits DispatchMessage(), which dispatches the message to the relevant window procedure – a longwinded way of saying that it looks up the WndProc function for the relevant window and calls it. Inside the edit control, in this case, there’s code a la

LRESULT CALLBACK WindowProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
     switch (nMsg)
    {
        case WM_SETTEXT:
            // better change our text!
            SetText( static_cast<LPCTSTR>(lParam) );
            InvalidateRect(hWnd,NULL);
            UpdateWindow(hWnd);
            return 0;
        …
     }
    return DefWindowProc(hWnd,nMsg,wParam,lParam);
}

In any case I’ve banged on enough about this style of coding. You get the idea.

Then frameworks started to arrive. Developers love frameworks. Most love writing frameworks more than using frameworks, since there’s less thought and frustration involved. A notable framework in this arena was MFC (Microsoft Foundation Classes), which basically wrapped up all this Win32 API stuff in classes. So now you could just call:

m_editBox.SetText(“Hello, world…”);

Rather than sending messages. Well, hurrah. Of course one can guess what the implementation of this SetText() function would be? Yep, it sends a message for you. Now clearly this is the most trivial example, and there are rather more complex situations than this. But MFC was good in that it was predictable. It was a thin vineer around the underlying controls, and didn’t pretend to be anything else. It wasn’t the most logical framework for beginners, since by and large you had to know what you were doing with the Windows API in order to appreciate what MFC was doing for you, and to know where to look for it to help you out. But it therefore didn’t get in the way; if things were behaving oddly, it was usually the underling control behaving oddly rather than MFC.

Nowadays we have even shinier frameworks such as .NET. These provide us with even nicer and easier ways of writing the same old applications. But all they’re really doing in the case of interacting with Windows controls is getting between the developer and the underlying control’s window procedure. Whether they do so usefully tends to vary.

As an example of this, and stepping more directly towards my point: List boxes, combo boxes and the like maintain lists of items to display. These items are just text, with optional associated user (application) data. These controls allocated their own memory to store their lists – thus the controls own the lists. The application tells the control to add items, remove them, select them, etc., all still by posting messages.

Now in MFC there were classes such as CComboBox to save you writing SendMessage() all the time. Splendid. So rather than sending a message to add a string to the list:

SendMessage(hWndComboBox, CB_ADDSTRING, 0, static_cast<LPARAM>(_T(“Hello, world…”)));

 I can say:

CComboBox.AddString(“Hello, world…”);

Jolly good. I can remove them as well – CB_DELETESTRING or, in MFC land, RemoveString(). Jolly good. But one noticable thing with combo boxes (and list boxes, come to that) is you can’t edit an item. There’s no CB_EDITSTRING or CB_REPLACESTRING. And as MFC is a thin vineer, it doesn’t fake up such functionality for you. You get what Win32 gives you, and like it.

So if you want to edit an item in a list, you have to remove it, and add back the changed version. Pretty obvious really. Equally obvious in MFC, since there’s no other choice.

Now on to .NET. .NET likes to be friendly. Rather than just giving you methods to add and remove items from a combo box, it exposes the collection of items to you for you to play with. Moreover, you don’t have to give it strings and then poke in user data. No, you can give it objects, and it’ll convert them to strings when it needs to. (Remember those last four words, they’re fairly central to my point.)

So you can have a class:

class ComboBoxItem
{
        private string m_Text;

        public ComboBoxItem(string text)
        {
            m_Text = text;
        }

        public override string ToString()
        {
            return m_Text;
        }

        public string Text
        {
            get { return m_Text; }
            set { m_Text = value; }
        }
}

And simply add that to the combo box directly:

comboBox1.Items.Add( new ComboBoxItem(“Hello, world…”) )

Now in this case my object isn’t particularly handy, but let’s pretend it’s actually a wrapper around something more complex – were this a list of usernames, one could add the actual user objects to the combo box. This can be very handy as when the end user selects items from the combo box there’s no need to go look up the user with that name or at that index in the list – it’s right there.

So now I have not only add and remove functionality, but edit functionality. If I want to change the text of an item in the list, I can simply do as follows:

((ComboBoxItem)comboBox1.SelectedItem).Text = “Goodbye, world…”

And bingo!

Nothing happens.

See, .NET isn’t as clever as it makes out. Underlying it all is still your basic combo box with its simple window procedure which still only knows how to add and remove strings. Moreover, .NET doesn’t make any attempt to work out whether your items’ text has changed when the combo box is next displayed.

Normally this would be fairly apparent.  You change the next, and next time you look at the combo there’s no change. So, d’uh. Zero marks to .NET for implying through its lovely collection and use of ToString() that it might be clever and able to update items in the combo box.

However this problem wasn’t apparent in the case I mentioned at the start of this rant. I was drawing the combo box items myself via owner draw, and so my combo box did appear to notice the edited text. However my drawing code essentially did a:

e.Graphics.DrawString(font,brush,((ComboBoxItem)comboBox1.SelectedItem).Text);

Which seems fairly logical, but is naturally always going to pick up the changed text, since it’s going straight to it. But at the same time the underlying control’s string list has not been updated. So when the user picked an item from the list, the text which went into the combo box’s text area was the real underlying string in the control’s list (which hadn’t changed since the item was created, with the first character the user typed). The .NET combo box’s item list does not necessarily correspond to the underlying control’s string list. Hence my problem.

This should be blindingly obvious. If .NET didn’t provide a shiny Items collection, instead just providing MFC style AddString() and RemoveString(), it would have been blindingly obvious. But in using these nice shiny frameworks the developer will usually remember that there’s a simple Windows control beneath it, but is likely to forget the semantic difficulties in interacting with that control natively, as the developer isn’t supposed to remember: they can just use the nice shiny framework which’ll sort it all out. But obfuscating limitations of the underlying control by providing a nice friendly but not fully functional interface is not my idea of helpful.