Introduction

Rarely in the field of coding has so much energy been expended to produce so little. This problem is one of those itches that much be scratched; though trivial, it cannot be left alone until solved - something only true nit-pickers can appreciate. The problem is to create a "disabled" combo box which allows you to select the text in the edit box, or read-only combo box if you prefer.

Background

The fundamental problem is that, once you make a combo read-only by conventional means (disabling), you can't do anything with the text. This is frustrating because sometimes selection-and-copying is useful. There's an article by Tim McColl here which changes the edit color of a disabled combo to make it more readable, but doesn't allow selection. In one of my own projects, I did get as far as making the text in the edit box selectable without actually disabling the combo box; I simply blocked mouse clicks on the arrow and key presses in the edit in "read-only" mode. But then the problem morphed into an aesthetic one: the arrow remained frustratingly "enabled".

The (supposedly definitive) solution which I originally posted to this problem, which lets us face it, was a little bit ugly, and consisted in recreating the combo box as a "simple" combo and indulging in various forms of evil voodoo to make it look like a drop-down one. The idea came from Rich Frank, who had used Paul S. Vickery's RecreateComboBox function to change the combo type between simple and drop-down, and thus hide the arrow when he didn't want any editing - not quite what I wanted, but it didn't take much to imagine the rest - just paint the arrow in and voila.

All very devious, but its lack of elegance suggested that there had to be a better way. There was, and it was blindingly obvious. When the combo paints itself, just mask out the arrow part to prevent the enabled arrow being painted, and then paint in the disabled one instead.

Actually, this solution does create another minor problem, namely that the drop-list-type combos are no longer amenable to the read-only treatment, since they don't have edit controls, or indeed any other sort of child control that might allow selecting. This is pretty easy to solve, but involves a slightly dirty trick. Both drop-down and drop-list combos need to be created with the drop-down style, so that there's always an edit for us to play with. A flag, set in the handler, then determines the combo type. The handling is almost the same: the read-only mode is identical, and for editable drop-lists, the only thing we do is block more keys to stop the text from changing.

If you're wondering what I mean by "handler", it refers to the fact that I decided to avoid implementing this functionality in a derived class and use subclassing instead. See the section No Inheritance Required below.

How it works

First, what we need to do is make the normal "enabled" arrow disappear when we're in read-only mode. It's frighteningly simple once you know how. First, you need to know the exact position and dimensions of the combo arrow, which is not quite as trivial as it sounds. I hacked around for ages with borders and edges, trying to get the damn thing to work with all the Windows XP themes I could throw at it, as well as in the Windows classic mode, but no. It was never quite right. I tried the theme metrics functions, but I could never get them to work at all, for some reason. Then I discovered GetComboBoxInfo(), and proceeded to kick myself quite hard. Where had that function been all my life? It gives you all the dimensions in three lines.

You just call this before calling the standard paint function, and bob's your uncle. OK, so having made the arrow disappear, all that remains is to paint a new, disabled one. The only complicating factor is the handling of XP themes. To do this, I've made use of a modified version of David Yuheng Zhao's Visual XP Styles class, which safely wraps the calls to the XP themes DLL. I've made some changes, in particular, in the routine which checks whether themes are in use. I've found that you need to check the version of the ComCtl32 DLL loaded by the app in addition to other checks, to be sure that themes are activated. Here, by the way, in GetComboArrowRect(), is where we make use of that nice function GetComboBoxInfo().

Another point worth commenting on is that, to prevent the arrow keys being used to change the combo selection, we also need to install a handler for the edit box and use it to block these key presses when the edit is read-only. Previously, I used a derived edit class, but this is much cleaner. The handling of key presses is a little more refined now too; you can always use the arrow keys for selection, and you can use the up/down keys to change the selection in drop-list mode, even though you're prevented from editing. CTRL-C (or whatever the local combination is) should also work for copying, and I think I managed to stop all backspaces and deletes. As a final touch, the edit handler also blocks the cut and paste commands when in read-only mode.

One caveat: the code is expecting a drop-down or drop-list combo only. Using it with a "simple" combo makes little sense; I'm not sure if the results would be benign or not. I advise against it. In fact, using it with any type other than drop-down is a little pointless, since the drop-list style doesn't give you an edit and thus prevents you from selecting text, which is the whole point of this exercise.

In this update, I've changed the CComboBox pointer to an HWND to avoid problems with window maps when this class is used in a DLL.

No Inheritance Required

From version v1.1, all this is implemented in a "plug-in" handler class, rather than as a derived combo box class. This allows you to add the read-only functionality to any combo class, including your own derived MuchBetterSuperWidgetCombo classes. Well, that's the nice theory anyway. To do this, the handler replaces the combo's WindowProc with its own custom procedure to make the required modifications, doing the same for the combo's edit control. I've essentially copied this idea from GipsySoft's BuddyButton, but implemented it in a class rather than as a global function. I've also included a facility to subclass the combo edit control, because it was something I needed to do. SubclassComboEdit() and UnsubclassComboEdit() will do your subclassing and hook and unhook my handlers accordingly to make everything work. Adding the same functions for the combo would be simple, but I haven't done it.

Using the code

Pretty easy really. You just create an instance of the CReadOnlyComboHandler class and initialize it with your combo by calling Init(pMyCombo). You call SetReadOnly(true) on the handler to make it read-only. If you want a "drop-list"-style combo, create the combo with the drop-down style, and call SetDropListMode(true). You can, of course, include the handler as a member of your own derived combo class if you wish. I suppose I should include a derived combo class with the handler as a member for those who don't have their own derived classes and don't want to have to keep track of combo handlers. Maybe next year .

I'd love to hear of people's experiences with it, as well as reports of bugs, problems, improvements, etc.

Debts to pay

I have consulted or used a frightening amount of other people's code to accomplish such a modest objective. In particular:

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

Comments and Discussions

A lot of code for a little effect,
no problem for me but i think a bit more non-XP support is always good, as long as you know how.

I actually wondered that a prof. developer didnt find an easier solution because hey, im still in school ^_^"

I do not know how exactly the following code looks like in MFC, since I use a library developed by myself, so I'll just write it in WinAPI:

use the following code if you want to make your CBS_DROPDOWN combobox be read-only, that means you can still select items from the list, and you can select the text in the combobox but you cant type/change it.

use the following code to disable the combobox, but let the user select the disabled text:

<font color=green>// hComboBox is the HWND of the ComboBox, hWnd of the main window</font>
COMBOBOXINFO cbi;
cbi.cbSize = <font color=blue>sizeof</font>(COMBOBOXINFO);
GetComboBoxInfo(hComboBox, &cbi); <font color=green>// Get the edit-ctrl handle "cbi.hwndItem"</font>
<font color=green>// Get the co-ords of the edit ctrl relative to our window, the owner window of the combobox</font>
POINT p = {0, 0};
ClientToScreen(cbi.hwndItem, &p);
ScreenToClient(hWnd, &p);
<font color=green>// change edit-ctrl's owner. Then the edit-ctrl is inside our window and not inside the combobox</font>
SetWindowLongPtr(cbi.hwndItem, GWL_HWNDPARENT, (LONG)hWnd); <font color=green>// use SetWindowLong with older VC++s'</font>
<font color=green>// place the edit-ctrl at same position as it was earlier inside the combobox</font>
SetWindowPos(cbi.hwndItem, NULL, p.x, p.y, 0, 0, SWP_NOACTIVATE|SWP_NOOWNERZORDER|SWP_NOSENDCHANGING|SWP_NOZORDER|SWP_NOSIZE);
<font color=green>// place the edit-ctrl over the combobox in the z-order</font>
SetWindowPos(hComboBox, cbi.hwndItem, 0, 0, 0, 0, SWP_NOACTIVATE|SWP_NOOWNERZORDER|SWP_NOSENDCHANGING|SWP_NOSIZE|SWP_NOMOVE);
<font color=green>// Disable the combobox. If you enable the combobox instead here (change the false to a true) then the code has same effect as the code above</font>
EnableWindow(hComboBox, <font color=blue>false</font>);
<font color=green>// Enable the edit ctrl</font>
EnableWindow(cbi.hwndItem, <font color=blue>true</font>);
<font color=green>// Make the edit ctrl read only</font>
SendMessage(cbi.hwndItem, EM_SETREADONLY, <font color=blue>true</font>, NULL);

A lot of code for a little effect,
no problem for me but i think a bit more non-XP support is always good, as long as you know how.

Well, my code has the effect of working. I'm not sure what you mean by the reference to non-XP support. This should work on any version of Windows.

I suspect changing the parent window would play havoc with many applications which override the combobox class and assume that the edit is a child of the combo window. I'm pretty sure it would fail badly in my application, anyhow, and, after all, I wrote this for myself initially. It's also specifically aimed at offering a plug-in solution for people who derive classes from CComboBox.

Incidentally, the documentation states:

You must not call SetWindowLong with the GWL_HWNDPARENT index to change the parent of a child window. Instead, use the SetParent function.

You must not call SetWindowLong with the GWL_HWNDPARENT index to change the parent of a child window. Instead, use the SetParent function

ow thank you =)

David Pritchard wrote:

This should work on any version of Windows.

a quote from that XP styles class article:
"This is a wrapper class to use the visual style APIs available in Windows XP."

Sry I'm a bit rediculous now nobody here should use win98 nowadays.. I just got used to make everything as cross-plattform as possible

David Pritchard wrote:

I suspect changing the parent window would play havoc with many applications which override the combobox class and assume that the edit is a child of the combo window. I'm pretty sure it would fail badly in my application

It did never matter for me before if the edit is a child of the combobox or not since GetComboBoxInfo always retrieves its handle, but yes yes its dangerous :->

"This is a wrapper class to use the visual style APIs available in Windows XP."

If you'd bothered to look at the actual code, or read the article properly, you'd realise that this wrapper, used carefully, does no harm at all in Windows versions prior to XP. You simply have to preface any use of the wrapper with a version check.

Perhaps a little more time spent reading and a little less trying to be smart might be in order, yes?

Well, you don't need the "Windows Server 2003" platform SDK, but you *do* need a recent Platform SDK (post-XP) to support XP theme-related stuff. I'm using the SDK from February 2003, but an earlier one would probably do.

I should make this clearer in the article. Perhaps I could just include the headers with the code and remove the dependency on the Platform SDK, I'm not sure. Still, I don't like including system headers very much, it's not very good practice.