News Archive

Sort posts by the categories below:

All Categories General Movies Sports Programming Woodworking

Read-Only Combo Boxes Posted at 07:09 AM - 10/06/09 in Programming by Dennis

Last year, I was writing a program where I needed some combo boxes that were "read-only".  What I mean by that is, if the user is authorized to make changes then the combo-box would operate as normal.  If not, then a read-only property was set, and the text portion of the combo couldn't be typed in and the drop down was disabled.  I finally found some code to help me out, I can't remember where it came from but it is very well documented:

   1:  using System;
   2:  using System.Runtime.InteropServices;
   3:  using System.Windows.Forms;
   4:  using System.ComponentModel;
   5:   
   6:  namespace ReadOnlyComboBoxTestApp
   7:  {
   8:      public class exComboBox : ComboBox
   9:      {
  10:   
  11:          #region "-- Declarations --"
  12:          private bool readOnly;
  13:          private bool droppedDown = false;
  14:          
  15:          private int selectedIndex = -1;
  16:          
  17:          private ComboBoxStyle dropDownStyle = ComboBoxStyle.DropDown;
  18:          
  19:          [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true, 
  20:              ExactSpelling = true)]
  21:          private static extern IntPtr GetWindow(IntPtr hwnd, UInt32 wCmd);
  22:   
  23:          [DllImport("user32.dll", CharSet = CharSet.Auto)]
  24:          private static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, 
  25:              bool wParam, Int32 lParam);
  26:          
  27:          private const UInt32 WM_COMMAND = 0x111;
  28:          private const UInt32 EM_SETREADONLY = 0xcf;
  29:          private const UInt32 EM_EMPTYUNDOBUFFER = 0xcd;
  30:          private const UInt32 CB_SHOWDROPDOWN = 0x14f;
  31:          private const UInt32 GW_CHILD = 5;
  32:   
  33:          #endregion
  34:          
  35:          #region "-- Properties --"
  36:   
  37:          [Browsable(true), DefaultValue(false)]
  38:          public bool ReadOnly
  39:          {
  40:              get { return readOnly; }
  41:              set
  42:              {
  43:                  // In design mode we don't want setting the read only property 
  44:                  // to alter the dropdown style.
  45:                  if (!DesignMode)
  46:                  {
  47:                      // If the DropDownStyle is anything other than DropDown then setting
  48:                      // ReadOnly to true will have no affect. Therefore we'll force the style
  49:                      // to DropDown as it goes to ReadOnly and restore it when it's turned off.
  50:                      if (value)
  51:                      {
  52:                          // If the value is changing then we want to save the dropdown style.
  53:                          // In case the value gets set to true more than once we don't want 
  54:                          // to lose the saved drop down style.
  55:                          if (readOnly != value)
  56:                          {
  57:                              dropDownStyle = base.DropDownStyle;
  58:                              base.DropDownStyle = ComboBoxStyle.DropDown;
  59:                          }
  60:                      }
  61:                      // restore the saved drop down style
  62:                      else
  63:                      {
  64:                          base.DropDownStyle = dropDownStyle;
  65:                      }
  66:                  }
  67:                  readOnly = value;
  68:                  
  69:                  // If readonly then don't let the user tab to the field
  70:                  base.TabStop = !value;
  71:                  
  72:                  // Setting TabStop to false causes the text in the box to be selected if it matches
  73:                  // an entry in the list. Setting selection length to zero removes the selection.
  74:                  base.SelectionLength = 0;
  75:                  
  76:                  // Send the textbox portion of the combo the readonly message.
  77:                  // It will change the color and behavior.
  78:                  IntPtr windowHandle = GetWindow(this.Handle, GW_CHILD);
  79:                  SendMessage(windowHandle, EM_SETREADONLY, value, 0);
  80:                  
  81:                  // If text was typed or pasted into the textbox, the context menu will
  82:                  // have the undo activated. When the text box is in the readonly state
  83:                  // the undo will still be active from the right click context menu
  84:                  // allowing the user to restore the previous value. This sendmessage
  85:                  // will clear the undo buffer which will clear the undo.
  86:                  SendMessage(windowHandle, EM_EMPTYUNDOBUFFER, value, 0);
  87:                  
  88:                  // the dropdown may have been dropped before the readonly is set
  89:                  droppedDown = false;
  90:                  this.Refresh();
  91:              }
  92:          }
  93:          
  94:          // Saving and returning a local copy of the selected index keeps a
  95:          // changed value from being returned when the control is in the
  96:          // readonly state. The OnSelectedIndexChanged event captures the
  97:          // index value that is returned here. Must be shadows or else the
  98:          // value passed won't cause the OnSelectedIndexChanged method to fire
  99:          // and the text value to be displayed, won't.
 100:          //
 101:          public new int SelectedIndex
 102:          {
 103:              get { return selectedIndex; }
 104:              set
 105:              {
 106:                  selectedIndex = value;
 107:                  base.SelectedIndex = value;
 108:                  // Set it twice to work around databound bug KB327244
 109:                  if (value == -1)
 110:                  {
 111:                      selectedIndex = value;
 112:                      base.SelectedIndex = value;
 113:                  }
 114:              }
 115:          }
 116:          #endregion
 117:   
 118:          #region "-- Overrides --"
 119:          
 120:          // Intercepting message 273 when readonly and the listbox is dropped
 121:          // keeps the user from selecting an item in the list and having it update
 122:          // the text value of the combo as well as firing the associated changed events.
 123:          // Since we intercept a windows message we will have to manually bring up
 124:          // the listbox.
 125:          //
 126:          // msg 305 (0x131)   =  an item was clicked from the dropdown list
 127:          // msg 273 (0x111)   = (WM_COMMAND) follows dropdown list click and all other actions?
 128:          // msg 8465 (0x2111) = (WM_REFLECT + WM_COMMAND) subsequent command after the 273
 129:          //
 130:          protected override void WndProc(ref Message m)
 131:          {
 132:              // Cannot use me.DroppedDown, it causes a System.StackOverflowException.
 133:              // Asking for it's value must produce windows messages for the combobox
 134:              // and thus create a recursive loop.
 135:              if (readOnly & m.Msg == WM_COMMAND) return;
 136:   
 137:   
 138:              base.WndProc(ref m);
 139:          }
 140:          
 141:          // This event will not fire when msg 273 is intercepted in WndProc. When in the
 142:          // readonly state clicking on an item in the listbox appears to have no effect. It
 143:          // does in fact change the value of MyBase.SelectedIndex. Saving the last good index
 144:          // value locally allows the overriden Index property to supply the proper index value.
 145:          //
 146:          protected override void OnSelectedIndexChanged(System.EventArgs e)
 147:          {
 148:              selectedIndex = base.SelectedIndex;
 149:              base.OnSelectedIndexChanged(e);
 150:          }
 151:   
 152:          // We must manually track dropped state. Asking the control if it's
 153:          // dropped from within WndProc will cause a System.StackOverflowException.
 154:          //
 155:          protected override void OnDropDown(System.EventArgs e)
 156:          {
 157:              droppedDown = true;
 158:              base.OnDropDown(e);
 159:          }
 160:          
 161:          // The up and down arrow keys cause the combobox to change selection to the next
 162:          // or previous in the list. The page up and page down keys change the selection
 163:          // by one page at a time as defined by the size of the dropdown list. Setting
 164:          // e.Handled to true if any of these keys is pressed stops the selection change
 165:          // when readonly. The alt down arrow combination is allowed since it drops the listbox.
 166:          //
 167:          protected override void OnKeyDown( KeyEventArgs e)
 168:          {
 169:              if (readOnly)
 170:              {
 171:                  if (e.KeyCode == Keys.Up || e.KeyCode == Keys.PageUp || 
 172:                      e.KeyCode == Keys.PageDown || (e.KeyCode == Keys.Down & 
 173:                      ((Control.ModifierKeys & Keys.Alt) != Keys.Alt)))
 174:                  {
 175:                      e.Handled = true;
 176:                  }
 177:              }
 178:              base.OnKeyDown(e);
 179:          }
 180:          
 181:          // The combobox default behavior when pressing F4 is to drop the listbox.
 182:          // If F4 is immediately pressed a second time the OnSelectionChangeCommitted
 183:          // event fires regardless of whether a change has been made or not. When
 184:          // readonly we don't want a change event to fire. This code will stop it.
 185:          //
 186:          protected override void OnSelectionChangeCommitted(System.EventArgs e)
 187:          {
 188:              if (!readOnly)
 189:              {
 190:                  base.OnSelectionChangeCommitted(e);
 191:              }
 192:          }
 193:          #endregion
 194:      }
 195:   
 196:  }

Here's the implementation:

   1:          private void button1_Click(object sender, EventArgs e)
   2:          {
   3:              exComboBox1.ReadOnly = true;
   4:          }
   5:   
   6:          private void button2_Click(object sender, EventArgs e)
   7:          {
   8:              exComboBox1.ReadOnly = false;
   9:          }
  10:   

Comments: 8

Previous Posts
TitleDate
A Boring Blog07:30 AM - 08/11/09
I give up!06:44 AM - 08/06/09
No more Duncan...09:21 PM - 07/22/09
Vacation: Days 4 & 505:53 AM - 07/04/09
Vacation: Days 2 & 305:59 PM - 07/02/09
Vacation: Day 107:20 AM - 06/30/09
Happy Father's Day!01:01 PM - 06/21/09
See, I'm not the only one06:55 AM - 06/20/09
Just Plain Mad!09:07 PM - 06/06/09
Irritated09:09 AM - 06/04/09
More flags! More Fun!02:26 PM - 06/02/09
I've been Taken!08:35 PM - 05/24/09
Let's Celebrate!05:57 AM - 05/20/09
Happy Mother's Day!05:46 AM - 05/11/09
Up in smoke09:39 PM - 04/30/09
He is risen!02:01 PM - 04/12/09
Proof that whiners get their way09:06 PM - 04/02/09
A few more Josephs04:40 PM - 03/29/09
Bad Dog! No Biscuit!08:39 AM - 03/27/09
OMG! They killed Kenny!08:12 PM - 03/25/09
Is it just me?06:14 AM - 03/24/09
Whatcha been up to?08:11 PM - 03/08/09
Mmmmhhhhhmmmm!09:34 PM - 02/15/09
A Question...07:35 PM - 02/13/09
Am I the only one?09:08 PM - 02/09/09