Click here to Skip to main content
Click here to Skip to main content

ComboBox with read only behavior

By , 25 Apr 2005
 

Contents

Introduction

ReadReadOnly behavior of the TextBox

TextBox controls have the nice read only feature. When you set the ReadOnly property of a TextBox to true, you can't edit the text in the TextBox anymore. The user can still select the text and copy it to another place. The background of the TextBox is painted gray so the user has a visual indication of the ReadOnly mode. The text is still painted in black, as in a normal editable TextBox.

This behavior is completely different to Enabled = false. Via the Enabled property, you can completely disable the TextBox. The text is not painted in good readable black anymore but in a gray color. And you can't select the text anymore.

Read only mode of TextBox

The ReadOnly mode is very usable. I often use it, when I have a form with several controls where the user can edit the content of controls depending on his privileges. If the user doesn't have the required privileges, I do not disable the controls but set them to ReadOnly. This way, the user can still very well read the texts in the TextBoxes, because their text is still painted in black.

No ReadOnly property for ComboBoxes

Most other .NET controls don't have a ReadOnly property. So if you want them to be read only, you have to disable them (Enabled = false). What I wanted was a ComboBox that offers a ReadOnly property and mimics the read only behavior of the TextBox. Especially, paint the text in good readable black instead of the disabled color gray.

DropDown Styles

Several people on the net argue, you don't need a ReadOnlyComboBox, because a ComboBox is read only when you set the DropDownStyle of the ComboBox to DropDownList. Well, of course, doing this, the user cannot edit the text in the ComboBox anymore. But he can still select a value from the ComboBox. For me, that's not the same thing as ReadOnly.

Solution

Idea

I tried different things like hooking into the WndProc to capture all the keyboard and mouse events and handle them myself, if the ComboBox is ReadOnly. But it just did not work out. I'm sure I did something wrong, but anyway. I'd also had to do the painting myself, where I had some problems as well.

Finally, I came up with a completely different idea. Whenever the ComboBox is set to ReadOnly = true, I display a TextBox instead that is read only. Because when it's read only, I don't need the dropdown button at all so a TextBox fits everything needed here.

Read only mode of ComboBox

Implementation

Basics

I found two implementation possibilities:

User control decorating the ComboBox and the TextBox

I could have created a new user control having a ComboBox and a TextBox at the same position. The user control acts as a decorator to the two embedded controls, and depending on the ReadOnly property, acts as a TextBox or a ComboBox.

I decided not to do that. Why should I do all the work to decorate the ComboBox? So I came up with solution two:

Inherit from ComboBox and just decorate the TextBox

I create a new control (ReadOnlyComboBox) that inherits from the standard ComboBox control and contains a TextBox. Whenever the ReadOnlyComboBox is set to read only, it acts as a decorator for the embedded TextBox and displays the TextBox instead of the ComboBox.

ReadOnly

First, I added a new property ReadOnly that handles the read only state.

public bool ReadOnly
{
  get { return _isReadOnly; }
  set
  {
    if (value != _isReadOnly)
    { 
      _isReadOnly = value;

      ShowControl();
    }
  }
}

The ShowControl() method is responsible to show either the ComboBox or the TextBox depending on the ReadOnly and the Enabled properties.

private void ShowControl()
{
  if (_isReadOnly)
  {
    _textbox.Visible = _visible && this.Enabled;
    base.Visible = _visible && !this.Enabled;
    _textbox.Text = this.Text;
  }
  else
  {
    _textbox.Visible = false;
    base.Visible = _visible;
  }
}

Visible

The ComboBox already has a Visible property. But we have to implement our own Visible property to store the control's visibility. The reason is simple: when we use the ReadOnly property, we change the visibility of the ComboBox. Thus the Visible property of the ComboBox does not correspond to the visibility of the ReadOnlyComboBox. That's why I declare a new Visible property that hides the original Visible property of the ComboBox.

public new bool Visible
{
  get { return _visible; }
  set
  {
    _visible = value;
    ShowControl();
  }
}

But just replacing the Visible property is not enough. The two methods Show() and Hide() need to be replaced as well, to use our new Visible property.

public new void Show()
{
  this.Visible = true;
}
  
public new void Hide()
{
  this.Visible = false;
}

Add the TextBox

While we have already used our TextBox, we have not yet created it. We'll do that in the constructor.

public ReadOnlyComboBox()
{
  _textbox = new TextBox();
}

But that's not enough. We have to add the TextBox to the parent container and we have to copy several properties of the ComboBox to the TextBox. To add the TextBox, we override OnParentChanged() of the ComboBox.

protected override void OnParentChanged(EventArgs e)
{
  base.OnParentChanged(e);
   
  if (Parent != null)
    AddTextbox();
  _textbox.Parent = this.Parent;
}
   
private void AddTextbox()
{
  _textbox.ReadOnly = true;
  _textbox.Location = this.Location;
  _textbox.Size = this.Size;
  _textbox.Dock = this.Dock;
  _textbox.Anchor = this.Anchor;
  _textbox.Enabled = this.Enabled;
  _textbox.Visible = this.Visible;
  _textbox.RightToLeft = this.RightToLeft;
  _textbox.Font = this.Font;
  _textbox.Text = this.Text;
  _textbox.TabStop = this.TabStop;
  _textbox.TabIndex = this.TabIndex;
}

Rest

In the AddTextbox() method, we make sure that the TextBox has the same behavior (like size, location, docking, anchor, etc.) like the ComboBox. But what if one of these properties changes during runtime? We have to make sure that such changes are propagated from the ComboBox to the TextBox. Therefore, I override several of the OnXXXX() methods of the ComboBox. Just a few of the overridden methods as example:

protected override void OnSelectedIndexChanged(EventArgs e)
{
  base.OnSelectedIndexChanged(e);
  if (this.SelectedItem == null)
    _textbox.Clear();
  else
    _textbox.Text = this.SelectedItem.ToString();
}
  
protected override void OnEnabledChanged(EventArgs e)
{
  base.OnEnabledChanged(e);
  ShowControl();
}
  
protected override void OnDropDownStyleChanged(EventArgs e)
{
  base.OnDropDownStyleChanged(e);
  _textbox.Text = this.Text;
}
  
protected override void OnResize(EventArgs e)
{
  base.OnResize(e);
  _textbox.Size = this.Size;
}
  
protected override void OnLocationChanged(EventArgs e)
{
  base.OnLocationChanged(e);
  _textbox.Location = this.Location;
}

That's it. We finally have our ReadOnlyComboBox. In the same way, you could do a ReadOnlyDateTimePicker or a ReadOnlyNumericalUpDown and so on.

I will develop more read only enabled controls in the future. You'll find them here. Of course, if I have to implement one of them in a different way, I'll put it to CodeProject again.

History

  • 03/28/2005: Initial version.
  • 04/21/2005: First update.
    • Added: Show() and Hide() methods because the Show() and Hide() methods of the parent ComboBox did not use our new Visible property but the Visible property of the parent control.
    • Bugfix: OnSelectedIndexChanged raised an exception when no item was selected (thanks to Alexandre Cunha for reporting this bug).
    • Bugfix: The TextBox is now added in the OnParentChanged() method. That solves several problems compared to the old solution where it was added in the ReadOnly property (thanks again to Alexandre Cunha for reporting one of these problems).
    • Changed: When the ComboBox was disabled, it showed a disabled ComboBox as it should be. When it was disabled and at the same time ReadOnly was true, it showed a disabled TextBox. When disabled, it now always shows a disabled ComboBox.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Claudio Grazioli
Team Leader
Switzerland Switzerland
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralOne more problem solved - no more draging from Data Sources problemsmemberTiago Freitas Leal19 Oct '09 - 13:19 
Besides the problems:
- Enter event missing
- Dispose method must be overriden
 
there is still a problem to be solved. Draging from Data Sources results in error
An error occurred while performing the drop:
 
Index 0 is out of range.
Parameter name: index
You must correct the OnParentChanged event handler as follows
protected override void OnParentChanged(EventArgs e)
{
    base.OnParentChanged(e);
 
    if (Parent != null)
    {
        AddTextbox();
        _textbox.Parent = Parent;
    }
}

AnswerRead Only mode's "Enter" event missingmemberjasonhammertime2 Apr '08 - 4:21 
I personally like the ReadOnly ComboBox. I can't believe MS took away the "Locked" property when migrating from VB6 to .NET. "Locked" was soooooo Accessible. And now it's sooooo not Accessible.
Moving on, I noticed a little bug. That when the ReadOnly property is set to true and the textbox is showing, the Enter event is not firing. I added an Enter event for the ReadOnly textbox. When the textbox Enter event fires it passes the event through the comboBox so the event on the form fires. Now I can show the read only data in my status bar when the control gains focus.
 
            public ComboBox_ReadOnly()
            {
                  _textbox = new TextBox();
                  //ENTER EVENT NEEDED FOR TEXTBOX
                  //WHEN CONTROL IS READ ONLY, THE ENTER EVENT WILL POPULATE STATUS BAR
                  _textbox.Enter += new System.EventHandler(TextBox_Enter);
            }
 
            private void TextBox_Enter(object sender, EventArgs e)
            {
                  this.OnEnter(e);
            }
 
            protected override void OnEnter(EventArgs e)
            {
                  //this method is called by ComboBox
                  //I needed to find the textbox.Enter event then call comboBox.Enter event
                  base.OnEnter(e);
            }
GeneralIncompatible with layout panelsmemberChuck_Esterbrook27 Dec '06 - 12:44 
I like the idea of swapping in the read only text box. However, it does not work well with a TableFlowLayoutPanel. The extra control (the text box) gets added into a separate cell upon "_textBox.Parent = ...".
 
I don't know of a fix off the top of my head... Maybe the text box could be used for its drawing code without actually be added to the parent? I've worked with a few GUI frameworks and that approach is feasible in some, but I don't know if it's feasible in WinForms.

Generalprinting.....membersam8sam21 Jun '06 - 20:49 
Did you check what happens when you try to print after setting the readonly property to true??? the combo doesnt show anything.....
 
samsam
GeneralKeyDown event handlermemberoskaremil24 May '06 - 3:19 
Why not just assign this eventhandler to the KeyDown-event of the combobox ?
 
private void On_ComboBox_KeyDown(object sender, KeyEventArgs e)
{
if((e.KeyData != Keys.Up) && (e.KeyData != Keys.Down))
e.SuppressKeyPress = true;
}
 
This supresses all keys except the UP/DOWN arrow keys.
 
Oskar
GeneralRe: KeyDown event handlermemberClaudio Grazioli24 May '06 - 6:38 
Sure. But it doesn't give the combobox a read only look and feel.
 
For me it was important that in a form that is displayed read only to user (because he for example does not have sufficient access rights to edit the values), all controls look read only.
 
And additionaly, with your proposal, the user could still drop down the combobox and change the value.
 
Claudio
Claudio's Website
Hommingberger Gepardenforelle
GeneralRe: KeyDown event handlermemberoskaremil24 May '06 - 10:41 
Ah ok. I thought the problem was denying users editing of the values, not changing the values.
 
Oskar
General[Dispose] method must be overriden...memberKSM31 Mar '06 - 1:10 
You are smart and excellent Smile | :)
 

Protected Overrides Sub Dispose(ByVal disposing As Boolean)
MyBase.Dispose(disposing)
_textbox.Dispose()
End Sub
GeneralUnable to update on dropdownlist stylememberJohn Choo1 Mar '06 - 2:02 
i have bind the combobox to dataset and add two item index for selection when readonly=false as below
 
Dim bindStatus As Binding = New Binding("text", ._ds, ._ds.Tables(0).TableName & ".Status")
Cb_Status.DataBindings.Add(bindStatus)
 
cb_status.items.add("Marriage")
cb_status.items.add("Single")
 
after select another selecteditem
if style is dropdown , update is ok ; but if i change to dropdownlist ...
 
it pop up ("No data change") message. (why datarow version didn't modify)
 
With myDataset
If .HasChanges Then
.....update statement ......
Else
msgbox("No data change")
End If
End with
 
pls help , what's wrong ?
 
John
 
-- modified at 11:08 Sunday 5th March, 2006
GeneralWhy not...memberAlexSoh21 Dec '05 - 5:43 
Well, I was searching around for a better solution but all the work involve in this is an overkill i think. When the ReadOnly property is set to True, why not just implement the KeyPress event and set the e.Handled to True??
 
Sorry if my answer is ridiculous since I didn't read the whole article, maybe this article was for something more.
 
Alex
GeneralRe: Why not...memberClaudio Grazioli21 Dec '05 - 6:31 
Well, sure this would be a posibility.
 
For me the following is important as well: If I have a form with several controls and all are readonly, they all should look like readonly. If you just capture the KeyPress event, the look of your combobox is still not readonly.
 
As it turned out, the problem is not solved by just changing the background color of the control.
 
That's why I went this road and not yours (well actually, your suggestion was the first thing I did, but than was stuck with the background color).
 
Cheers
Claudio
 
Claudio
Claudio's Website
Hommingberger Gepardenforelle
GeneralRe: Why not...memberAlexSoh21 Dec '05 - 6:48 
Oh yeah. I see what u are trying to accomplish here. Thanks for the clarification.
GeneralProblems with data bindingsussAnonymous29 Sep '05 - 10:21 
I have two problems when I try data binding to this control.
 
1. Under some conditions that are hard to reproduce, if the form is first drawn with ReadOnly set to true, and the control is not drawn, once the control is drawn, the correct text is not displayed until the ReadOnly property is set to true and then to false again. As a solution, I implemented code to sets the data binding of the TextBox to that of the ComboBox when the ComboBox's DataBinding's collection is changed.
 
2. Once I did #1, then I realized that the format and parse events of the ComboBox need to be propagated to the TextBox in a similar fashion. I am finding this to be rather challenging. Please let me know your thought on this.
 
Thanks,
 
Nima Dilmahgani

GeneralRe: Problems with data bindingmemberClaudio Grazioli30 Sep '05 - 5:53 
Have you tried the tip of ExplodingBoy? http://www.codeproject.com/cs/miscctrl/ReadOnlyComboBox.asp?select=1233566&df=100&forumid=166927#xx1233566xx[^]
 
Claudio
Claudio's Website
Hommingberger Gepardenforelle
GeneralRe: Problems with data bindingsussNima Dilmaghani30 Sep '05 - 14:16 
Thanks, it does not make a difference in my situation.
 
I added the following code to change the data binding on the TextBox everytime the data binding on the ComboBox is changed and it works very well. But I have not been able to find a way to keep their format and parse events in sync in a similar fashion. (a format or parse event delegate does not give its secrets away easily).
 
in the constructor of the comboBox:
this.DataBindings.CollectionChanged += new CollectionChangeEventHandler
(DataBindings_CollectionChanged);
 

void DataBindings_CollectionChanged(object sender, CollectionChangeEventArgs e)
{
object o = e.Element;
if (o == null)
return;
Binding binding = o as Binding;
if (binding == null)
throw new Exception("ProjectMentor.Windows.Controls.ComboBox's collection was changed and the element was not a Binding object");

string propertyName = binding.PropertyName;
object dataSource = binding.DataSource;
string bindingMember = binding.BindingMemberInfo.BindingMember;
Binding newBinding = new Binding(propertyName, dataSource, bindingMember);
 
if (e.Action == CollectionChangeAction.Add)
{
_textbox.DataBindings.Add(newBinding);

}
else if (e.Action == CollectionChangeAction.Remove)
{
_textbox.DataBindings.Remove(newBinding);
}
}
GeneralSelectedItem does not work on OnSelectedIndexChangedmemberLalit Bhatia22 Sep '05 - 2:25 
If combo box is bound with DataTable then setting
_textbox.Text = this.SelectedItem.ToString();
does not work. Instead of text it shows System.Data.Dataview.
 
I used following code to work around this issue.
m_textbox.Text = this.Text;// this.SelectedItem.ToString();
 

GeneralRe: SelectedItem does not work on OnSelectedIndexChangedmemberIlíon25 Aug '06 - 17:52 
You might try something along these lines:
string itemText = "";
if (DisplayMember.Length > 0)
{
	System.Data.DataRowView drv = (System.Data.DataRowView)this.Items[args.Index];
	itemText = drv[this.DisplayMember].ToString();
}

GeneralRe: SelectedItem does not work on OnSelectedIndexChangedmembercsg120 Sep '06 - 11:23 
I also had this problem. I cycle through records with a navigator and the "System.Data.Dataview" showed up in some of the combo boxes, but not all, some of the time, not every time. I didn't take the time to try and narrow it down (not sure I would have understood what was going on if I had). But, Lalit's workaround works great. Thanks for the read only combo box and for this workaround.
-Dan
GeneralSame code for VB.NETsussExplodingBoy19 Aug '05 - 7:09 
Just in case anyone was interested, I put it in VB format. Worked pretty well.
 
-------------------------------------------------------
 

Imports System.Windows.Forms
Public Class clsDeadBoxCombo
 
Inherits ComboBox
 
#Region " Declarations "
 
Private bIsReadOnly As Boolean
Private bIsVisible As Boolean
Private ThisTextBox As New TextBox
 
#End Region
 
#Region " Properties "
 
Public Property IsReadOnly() As Boolean
Get
Return bIsReadOnly
End Get
Set(ByVal Value As Boolean)
If Value <> bIsReadOnly Then
bIsReadOnly = Value
ShowControl()
End If
End Set
End Property
 
Public Property IsVisible() As Boolean
Get
Return bIsVisible
End Get
Set(ByVal Value As Boolean)
bIsVisible = Value
ShowControl()
End Set
End Property
 
#End Region
 
Private Sub ShowControl()
If bIsReadOnly Then
ThisTextBox.Visible = bIsVisible And Me.Enabled
MyBase.Visible = bIsVisible And Not Me.Enabled
ThisTextBox.Text = Me.Text
ThisTextBox.BackColor = System.Drawing.SystemColors.Control
Else
ThisTextBox.Visible = False
MyBase.Visible = bIsVisible
ThisTextBox.BackColor = System.Drawing.SystemColors.Window
End If
End Sub
 
Public Overloads Sub Show()
Me.Visible = True
End Sub
 
Public Overloads Sub Hide()
Me.Visible = False
End Sub
 
Protected Overrides Sub OnParentChanged(ByVal e As EventArgs)
MyBase.OnParentChanged(e)
 
If Not Parent Is Nothing Then
AddTextbox()
ThisTextBox.Parent = Me.Parent
End If
End Sub
 
Private Sub AddTextbox()
With ThisTextBox
.ReadOnly = True
.Location = Me.Location
.Size = Me.Size
.Dock = Me.Dock
.Anchor = Me.Anchor
.Enabled = Me.Enabled
.Visible = Me.Visible
.RightToLeft = Me.RightToLeft
.Font = Me.Font
.Text = Me.Text
.TabStop = Me.TabStop
.TabIndex = Me.TabIndex
End With
End Sub
 
Protected Overrides Sub OnSelectedIndexChanged(ByVal e As EventArgs)
MyBase.OnSelectedIndexChanged(e)
If Me.SelectedItem Is Nothing Then
ThisTextBox.Clear()
Else
ThisTextBox.Text = Me.SelectedItem.ToString()
End If
End Sub
 
Protected Overrides Sub OnEnabledChanged(ByVal e As EventArgs)
MyBase.OnEnabledChanged(e)
ShowControl()
End Sub
 
Protected Overrides Sub OnDropDownStyleChanged(ByVal e As EventArgs)
MyBase.OnDropDownStyleChanged(e)
ThisTextBox.Text = Me.Text
End Sub
 
Protected Overrides Sub OnResize(ByVal e As EventArgs)
MyBase.OnResize(e)
ThisTextBox.Size = Me.Size
End Sub
 
Protected Overrides Sub OnLocationChanged(ByVal e As EventArgs)
MyBase.OnLocationChanged(e)
ThisTextBox.Location = Me.Location
End Sub
 
End Class
 

Questionhow to create a property like thismemberisgrom3 Jul '05 - 18:10 
How do I create a property to mimic the dataadapter.selectcommand/dataadapter.selectcommand.parameters...
 
Example/illustration:
 
dim adp as new dataadapter
 
adp.SelectCommand 'this is a property
obj.SelectCommand.Parameters 'this is also a property
 
how is it possible to create a property within a property like this? I have model i'm working where I need to do this.
 
Thanks,
Glenn
 

AnswerRe: how to create a property like thissussAnonymous4 Jul '05 - 7:09 
I don't see the connection to the read only combobox here, but here's the answer anyway (at least I hope so):
 
class Test1
{
  ArrayList _parameters;
 
  ...
 
  public ArrayList Parameters
  {
    get { return _parameters; }
    set { _parameters = value; }
  }
}
  
class Test2
{
  Test1 _test1;
  
  ...
  
  public Test1 TestProperty
  {
    get { _return _test1; }
    set { _test1 = value; }
  }
 
  ...
}
Now you can things like the following:
 
Test2 test = new Test2;
Test1 test1 = test.Test1;
ArrayList list = test.Test1.Parameters;
hth
GeneralRead only combobox with dropdown listmemberPatric_J19 May '05 - 8:53 
Nice idea, it's definitely a useful control. However, I think it is more important to keep the dropdown list when going read only than introduce some copy to clipboard-functionality. My attempt don't allow a user to copy anything, but have the dropdown list. This was attempted by doing the ComboBox ownerdraw and paint all item so they look read only. If a user anyway changes selected item while it's read only I just set it back.
 
Here's the link to my code: Read only ComboBox[^]
 
I will one day attempt to add copy functionality to the Combobox directly, I have an idea of how to do it without too much coding.
 
/Patric
My C# blog: C# Coach[^]
GeneralRe: Read only combobox with dropdown listmemberClaudio Grazioli19 May '05 - 20:37 
Hi Patric
 
Of course that's an approach (like others already suggested in the comments) but your version has the problem of only supporting ComboBoxes with DropDownStyle = ComboBoxStyle.DropDownList.
 
My approach was to deliver a solution that always works. But I'm working on a similar approach like you at the moment but without the constraint and some other minor different approaches and will have that finished somewhen next week. I will put it either in my blog or here on CodeProject when it's ready.
 
Claudio
Claudio's Website
Hommingberger Gepardenforelle
GeneralI did it in a different way but not actually a readonlymembergarapatiSridhar4 May '05 - 22:10 
what i did is used two properties of the combobox one is accessible name and one is a tag
 
when i set the tag to readonly and accessiblename to the desired value, and when combobox is selected, i read the accessiblename and reset to the old value, even when the end user changes the values it will reset to the old. making it a readonly.
 
Sridhar
GeneralI do the same thing using keypress eventmemberxknown26 Apr '05 - 18:43 
That's the way I have a ComboBox with read only behavior:
 
[vb.net]
Private Sub comboBox1_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles cmbTipo.KeyPress
e.Handled = True
End Sub
 
[c#]
private void comboBox1_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
{
e.Handled = true;
}

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

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 26 Apr 2005
Article Copyright 2005 by Claudio Grazioli
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid