Click here to Skip to main content
15,860,943 members
Articles / Desktop Programming / MFC
Article

A Read-Only Combo with Selectable Text

Rate me:
Please Sign up or sign in to vote.
4.52/5 (14 votes)
30 Nov 20057 min read 102.2K   2.7K   32   15
A combo box which behaves as a fully disabled combo box except that it allows you to select the text.

ScreenShot.png

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.

Armed with this, the masking function is a doddle.

 void CReadOnlyComboHandler::MaskComboArrow (CDC& dc)
 {
  CRect rectArrow;
  
  if (m_bReadOnly)
  {
   rectArrow = GetComboArrowRect();
  
   // Exclude the arrow area from the paint update region
   dc.ExcludeClipRect(&rectArrow);
  }
 }

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().

void CReadOnlyComboHandler::DrawDisabledDropArrow(CDC* pDC)
{
 ASSERT(m_pCombo != NULL);
 
 // If read-only, draw a disabled down-arrow
 if (m_bReadOnly && m_pCombo && pDC)
 {
  CRect rectArrow = GetComboArrowRect();
 
  // Now make the arrow rect the clipping region
  // and paint the arrow
  CRgn rgnArrow;
  rgnArrow.CreateRectRgnIndirect(&rectArrow);
  pDC->SelectClipRgn(&rgnArrow);
 
  // Draw control at RHS of control
 
  // If app is themed...
  if (CVisualStylesXP::GetInstance()->IsAppUsingThemes())  
  {
   HTHEME hTheme = 
    CVisualStylesXP::GetInstance()->OpenThemeData(m_pCombo->GetSafeHwnd(), 
    L"COMBOBOX");

   CVisualStylesXP::GetInstance()->DrawThemeBackground(hTheme, 
                         pDC->GetSafeHdc(), CP_DROPDOWNBUTTON, 
                         CBXS_DISABLED, &rectArrow, NULL);
   CVisualStylesXP::GetInstance()->CloseThemeData(hTheme);
  }
  else
  {
   // Do the draw 
   pDC->DrawFrameControl(rectArrow, DFC_SCROLL, 
                         DFCS_SCROLLDOWN | DFCS_INACTIVE);
  }
 }

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:

History

  • v1.0 - 8 November 2004 - Posted.
  • v1.1 - 21 November 2004 - Re-implemented as an external handler class. Made compatible with drop-list combos.
  • v1.3 - 22 November 2005 - Massively delayed update. Combo no longer recreated, arrow fixed by masking. Now gets arrow dimensions correctly for all XP themes. Made combo into HWND.

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.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior)
Spain Spain
I'm originally from Leek, Staffordshire in the UK, but I now work as a C++/MFC developer in Madrid, Spain.

I followed an erratic study/career path from German to a PhD in something resembling political science and linguistics, eventually ending up in IT.

I'm still finding bustling streets, warm nights, beer and vitamin D a pretty heady combination.

Comments and Discussions

 
GeneralEasier solution [modified] Pin
ZoneShader22-Jul-07 0:05
ZoneShader22-Jul-07 0:05 
GeneralRe: Easier solution Pin
David Pritchard22-Jul-07 3:45
David Pritchard22-Jul-07 3:45 
GeneralRe: Easier solution Pin
ZoneShader22-Jul-07 22:56
ZoneShader22-Jul-07 22:56 
David Pritchard wrote:
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 :->

=ZSH

::Improving good places, so seekers find their fate
GeneralRe: Easier solution Pin
David Pritchard23-Jul-07 3:14
David Pritchard23-Jul-07 3:14 
GeneralCompiling Pin
cucubau5-Dec-05 20:21
cucubau5-Dec-05 20:21 
GeneralRe: Compiling Pin
cucubau5-Dec-05 20:51
cucubau5-Dec-05 20:51 
GeneralRe: Compiling Pin
David Pritchard5-Dec-05 22:52
David Pritchard5-Dec-05 22:52 
GeneralDropList style Pin
dbucci15-Nov-04 1:34
dbucci15-Nov-04 1:34 
GeneralRe: DropList style Pin
David Pritchard15-Nov-04 10:28
David Pritchard15-Nov-04 10:28 
GeneralRe: DropList style Pin
xufeisjtu15-Sep-05 14:47
xufeisjtu15-Sep-05 14:47 
GeneralRe: DropList style Pin
David Pritchard30-Nov-05 12:30
David Pritchard30-Nov-05 12:30 
GeneralGets my 5 Pin
.dan.g.8-Nov-04 14:56
professional.dan.g.8-Nov-04 14:56 
GeneralRe: Gets my 5 Pin
David Pritchard9-Nov-04 4:05
David Pritchard9-Nov-04 4:05 
GeneralGreat Pin
the-unforgiven8-Nov-04 8:00
the-unforgiven8-Nov-04 8:00 
GeneralRe: Great Pin
David Pritchard8-Nov-04 12:20
David Pritchard8-Nov-04 12:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.