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

A Complete Read Only ComboBox

By , 18 Jun 2012
 

Introduction

Like so many others, our developers were taken in with the look and feel of the TextBox's ReadOnly property. Compared to a disabled TextBox, it is legible yet conveys a change in the state of the control. You cannot change its value but if needed you can copy the text out of it. Since the ComboBox is an enhanced TextBox, it was natural to look for a ReadOnly property, but as you all know, it's not there. Oh, but it is there. It's not exposed in the Framework but it lies underneath and can be accessed with a single line of code. Well... sort of.

The essence of giving the look and feel of the ReadOnly property does take one line of code, but plugging the holes around it takes a few more. There are a couple of other articles here on The Code Project by Dan Anatoli and Claudio Grazioli that create the look of a ReadOnly state and some of its feel, but their solutions didn't give me what I expected in a read only ComboBox. What I expected was that it should still present the dropdown list in case the user wanted to see what the other selections were. It should allow copying text out of the box either with a Ctrl+C key press or by using a right click context menu. Of course, when in ReadOnly mode, it should not allow a change.

A Single Line of Code

Searching in Google has become a job requirement. I began the hunt for that magic combination of keywords that would lead me to a post where someone would give me a little known property or method that would solve my problem. Hopefully it would be a single line of code. In this case, it started out looking like it might be that simple.

SendMessage(GetWindow(Me.Handle, GW_CHILD), _
                      EM_SETREADONLY, Value, 0)

You can interact with Windows controls by sending messages to them. These messages are documented on MSDN. In the case of the ComboBox, you can send it the EM_SETREADONLY message. As soon as you do, the TextBox portion of the control looks and acts just like you'd expect; the color changes, you can't type in the box anymore, you can't paste into it, you can't use any of the editing keys, but... (and this is a big but) the ListBox portion of the control doesn't know a thing about what you have just done. It still allows you to select new values, changing the text in the box and the selected index of the list. This is where you have to start plugging the holes.

Plugging the Holes

There are four holes that are left after the TextBox within the combo has been placed in the ReadOnly state. They are:

  • Drop down list mouse selections
  • Undo in the right mouse button context menu
  • Using cursor keys to select from the list
  • Bringing up the list with repeated F4 presses

Drop Down List Mouse Selections

I wanted the user to be able to view the list even if she or he could not change the selection. There were two things that happen when you click on an item in the list. It changes the text in the text box and it changes the SelectedIndex. To stop the text from being changed I found that if I intercepted Windows message 273 when in a dropped down state, that would keep the text from changing. Message 273 also caused the list to be pulled up. So I had to send a message to the combo to tell it to bring up the list.

Protected Overrides Sub WndProc(_
              ByRef m As System.Windows.Forms.Message)
    If _ReadOnly AndAlso _DroppedDown Then
        If m.Msg = 273 Then
            _DroppedDown = False
            SendMessage(Me.Handle, CB_SHOWDROPDOWN, _
                       System.Convert.ToInt32(False), 0)
            Exit Sub
        End If
    End If
    MyBase.WndProc(m)
End Sub

That stopped the text from changing but not the SelectedIndex. To handle that, I had to take control of the SelectedIndex property. When message 273 is absorbed in the WndProc override the OnSelectedIndexChanged method is not called. By saving the value of SelectedIndex locally, I can have the last selected value to pass back in a shadowed SelectedIndex property. I had to shadow the property or else the OnSelectedIndexChanged method would not get called.

Protected Overrides Sub OnSelectedIndexChanged(_
                        ByVal e As System.EventArgs)
    _SelectedIndex = MyBase.SelectedIndex
    MyBase.OnSelectedIndexChanged(e)
End Sub

Public Shadows Property SelectedIndex() As Integer
    Get
        Return _SelectedIndex
    End Get
    Set(ByVal Value As Integer)
        _SelectedIndex = Value
        MyBase.SelectedIndex = Value
    End Set
End Property

Undo in the Right Mouse Button Context Menu

The right click context menu of the TextBox has an undo option. If the user were to paste in a value or hand type a change, the undo option will be enabled in the context menu. If there was an undo in progress when the control was placed in a ReadOnly state, it would be possible to get around the ReadOnly and revert to the previous value. But luckily this was one of those one line wonders.

SendMessage(GetWindow(Me.Handle, _
          GW_CHILD), EM_EMPTYUNDOBUFFER, Value, 0)

The SendMessage function causes the text box in the control to empty its undo buffer and disable the undo context menu selection. Next was the cursor keys.

Using Cursor Keys to Select from the List

Whether the list is dropped down or not, both the up and down cursor keys and the page up and down keys will allow you to change the selected value. This can be handled with a little override of the OnKeyDown method. Note that the Alt + Cursor Down key is allowed as it can be used to drop the list box.

Protected Overrides Sub OnKeyDown(_
        ByVal e As System.Windows.Forms.KeyEventArgs)
    If _ReadOnly Then
        If e.KeyCode = Keys.Up OrElse _
             e.KeyCode = Keys.PageUp OrElse _
             e.KeyCode = Keys.PageDown OrElse _
             (e.KeyCode = Keys.Down And _
               ((Me.ModifierKeys And Keys.Alt) <> Keys.Alt)) Then
           e.Handled = True
        End If
    End If
    MyBase.OnKeyDown(e)
End Sub

Bringing up the List with Repeated F4 Presses

Last was an odd bug in the ComboBox itself. If you press F4 the list will drop. If you press it again, it will bring up the list and fire the OnSelectedChangeCommitted event even though nothing has changed. Putting in the following code stops the event when the control is in the ReadOnly state. This does nothing for the inappropriate firing of the event when the control is writable.

Protected Overrides Sub OnSelectionChangeCommitted(_
                           ByVal e As System.EventArgs)
    If _ReadOnly Then
    Else
        MyBase.OnSelectionChangeCommitted(e)
    End If
End Sub

Using the Code 

The code was written as a class that can be added into a class library, compiled, and added to the toolbox. When I first tried this class out on the other developers where I work, I named the control ReadOnlyComboBox. I was immediately assaulted with comments such as "Why would you want a ComboBox that can only be read?" and "What's the point of giving them a list if they can never change the value?" OK, so it's not a ComboBox that is ReadOnly. It's a ComboBox with a ReadOnly property. I resorted to the time honored technique of adding an "X" to the inherited class name. In the sample project it is named xboXComboBox. All of our controls are prefixed with a three character designation, then a significant name after that.

By the way, the ZIP file contains the source and a test project. You may need to build the solution before you can view the test form in the designer.

2012 Update

As we've used the control on my team and as Code Project users have submitted comments there have been various fixes and enhancements.  When this article was written the current version of the .Net framework didn't include an auto complete feature in the ComboBox. It came as a surprise that  AutoCompleteMode opened a hole where the control could be changed when ReadOnly. The fix was simple by just setting AutoCompleteMode to None when going to ReadOnly and then restoring the previous value when coming back out.

There were several issues related to clearing the selection. The most complicated case came when the control was bound to a numeric property in a class. Clearing the control would place a -1 in SelectedIndex. The control would require the user to select an item from the list before they would be allowed to exit. The fix was to override the -1 with a zero so that the first item in the list is selected. This means that when binding to a numeric property it is not possible to clear the selection. Another option is to use a Nullable(Of) numeric property.  This will allow the ComboBox to properly handle clearing the selection. 

Summary

In putting this class together I thought I had something special. It wasn't large or complex but I did have to define a call to user32.dll and I did have to intercept and handle Windows messages. My understanding of Windows messages came from watching them and seeing what happened if I didn't pass them along. Does that make the code vulnerable? I don't think so. I look forward to your thoughts. So far our implementation has been successful and we are very pleased. It was great to find a way to interact with the underlying control and pull a few more horsepower out of it. For me, this was a more complete solution, handling all of the look and behavior that I expected from a ReadOnly property of a ComboBox

History 

  • 09/08/05
    • Initial release
  • 08/14/06
    • Ported from Visual Studio 2003 to Visual Studio 2005
    • Added support for DropDownStyle = DropDownList
    • Added workaround for setting SelectedIndex to -1 when data-bound, see KB327244
  • 01/23/08
    • Fixed bug which lost DropDownStyle when ReadOnly set to true two times in a row
  • 06/15/12
    • Disabled AutoCompleteMode when ReadOnly to prevent updates via the keyboard 
    • Force FormattingEnabled to prevent selection of the first item in the list after clearing  
    • Fixed roach motel behavior after clearing when control bound to a numeric property 
    • Converted comments to XML Summary and Remarks 

License

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

About the Author

Thomas Wells
Software Developer (Senior) Iowa Department of Transportation
United States United States
Member
Tom works for the DOT as a developer and project leader. Besides designing VB.Net SQL Server applications he specializes in creating tools, classes, and controls for the development team where he works.

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   
Questionit seems that the readonly feature would lost if set to autocomplete...memberjtxd31 May '12 - 2:00 
it seems that the readonly feature would lost if set to autocomplete...
 
cbo.AutoCompleteSource = AutoCompleteSource.ListItems
cbo.AutoCompleteMode = AutoCompleteMode.SuggestAppend
AnswerRe: it seems that the readonly feature would lost if set to autocomplete...memberThomas Wells31 May '12 - 12:25 
You're right! I wrote this against a version of the combobox that didn't have AutoComplete. I'll post an update when I figure out how to prevent AutoComplete changes.
AnswerRe: it seems that the readonly feature would lost if set to autocomplete...memberThomas Wells19 Jun '12 - 4:24 
Fixed. As the control is set to ReadOnly, the AutoCompleteMode is set to None. When ReadOnly is turned off, the previous value is restored. I updated the source code and included some other enhancements we have accumulated over the years. See the history.
 
Thanks for pointing this out. I had no idea it was broke.
QuestionDatabound and SelectionLength [modified]memberYoyosam27 Jan '12 - 10:55 
Great little upgrade that will serve my purpose if I can fix one little issue.
 
My form is used to create new Widgets and to edit existing ones. On new Widgets, I want the dropdown to be enabled. On existing Widgets, I don't want them to be able to change the value.
 
First I bind the control to my collection, then I check to see if the Widget is new, and if so I set the combobox property Readonly to True.
 
When the form loads, the text for the item that was selected during binding is highlighted.
 
I tried setting the SelectionLength to 0 after setting Readonly = True, but it's not having any effect.
 
Any ideas?
 
Edit... forget.. I am using Framework 4... as that might very well make a difference.

modified 27 Jan '12 - 17:25.

AnswerRe: Databound and SelectionLengthmemberThomas Wells27 Jan '12 - 12:21 
Lucky you. I've had this happen within the last month. In my case I was dynamically adding the ComboBox to a TableLayoutPanel. When displayed the text would be selected. It didn't matter if it was ReadOnly or not. Setting the selection length to 0 as I created it didn't help. I had to go back and set it to 0 in a later event. In my case I had to set it during the resize event. Each time I'd resize the form it would reselect. I don't know if my code is causing this or if it's in the base class.
GeneralRe: Databound and SelectionLengthmemberYoyosam27 Jan '12 - 15:09 
Funny you should mention the conditions that caused it in your case. It just so happens that I also have it in TableLayoutPanel. And I also found out later this afternoon that if I attemped to set SelectionLength to 0 on FormActive that it would clear it out.
 
So then I went into your class and added it to OnVisibleChanged and that seems to work. Resize event sounds like it might even be a better one.
 
Here's what I've added to your class for the moment.
 
Protected Overrides Sub OnResize(e As System.EventArgs)
        MyBase.OnResize(e)
        SelectionLength = 0
End Sub

GeneralRe: Databound and SelectionLengthmemberThomas Wells30 Jan '12 - 7:32 
I tried OnVisibleChanged and OnResize. It worked when re-sizing but not on the initial display.
SuggestionI added the faeture not to show Dropdown and readonly change eventmemberKOUKOSDD31 Aug '11 - 2:38 
On your code i added
 Public Event ReadOnlyChanged()
 
    <Browsable(True), DefaultValue(False)> _
    Public Property [ReadOnly]() As Boolean
        Get
            Return _ReadOnly
        End Get
        Set(ByVal Value As Boolean)
            ' In design mode we don't want setting the read only property 
            ' to alter the dropdown style.
            ' If Not DesignMode Then
            ' If the DropDownStyle is anything other than DropDown then setting
            ' ReadOnly to true will have no affect. Therefore we'll force the style
            ' to DropDown as it goes to ReadOnly and restore it when it's turned off.
            If Value = True Then
                ' If the value is changing then we want to save the dropdown style.
                ' In case the value gets set to true more than once we don't want 
                ' to loose the saved drop down style.
                If _ReadOnly <> Value Then
 
                    _DropDownStyle = MyBase.DropDownStyle
 
                    MyBase.DropDownStyle = ComboBoxStyle.DropDown
 

                End If
            Else    ' restore the saved drop down style
                MyBase.DropDownStyle = _DropDownStyle
            End If
            ' End If
            _ReadOnly = Value
            ' If readonly then don't let the user tab to the field
            MyBase.TabStop = Not Value
            ' Setting TabStop to false causes the text in the box to be selected if it matches
            ' an entry in the list. Setting selection length to zero removes the selection.
            MyBase.SelectionLength = 0
            ' Send the textbox portion of the combo the readonly message.
            ' It will change the color and behavior.
<big>'I DISABLED THIS LINE OF CODE BEACAUSE I HAD SOME PROBLEM WITH REFRESH.</big>
           <big> '  SendMessage(GetWindow(Me.Handle, GW_CHILD), EM_SETREADONLY, Value, 0)</big>
            ' If text was typed or pasted into the textbox, the context menu will
            ' have the undo activated. When the text box is in the readonly state
            ' the undo will still be active from the right click context menu
            ' allowing the user to restore the previous value. This sendmessage
            ' will clear the undo buffer which will clear the undo.
            SendMessage(GetWindow(Me.Handle, GW_CHILD), EM_EMPTYUNDOBUFFER, Value, 0)
            ' the dropdown may have been dropped before the readonly is set
            _DroppedDown = False
            Me.Refresh()
            RaiseEvent ReadOnlyChanged()
        End Set
    End Property
 

And I added a class that inherits yours with the following code
 
Imports System.ComponentModel
 
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Public Class ReadOnlyComboWithoutDrop
    Inherits YourClass
 
    
    <System.Diagnostics.DebuggerNonUserCode()> _
    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        Try
            If disposing AndAlso components IsNot Nothing Then
                components.Dispose()
            End If
        Finally
            MyBase.Dispose(disposing)
        End Try
    End Sub
 
    Private components As System.ComponentModel.IContainer
    Private _text As New TextBox
    Private _DropDownWhenReadOnly As Boolean = True
    Public Event DropDownWhenReadOnlyChanged()
 
 
    <System.Diagnostics.DebuggerStepThrough()> _
    Private Sub InitializeComponent()
        components = New System.ComponentModel.Container()
 
    End Sub
    Public Sub New()
 
    
        InitializeComponent()
 

     
        _text.Size = MyBase.Size
        _text.TabStop = MyBase.TabStop
        _text.BackColor = MyBase.BackColor
        _text.ForeColor = MyBase.ForeColor
        _text.Font = MyBase.Font
        _text.Multiline = True
        _text.ReadOnly = True
        _text.WordWrap = False
        _text.RightToLeft = MyBase.RightToLeft
        _text.TabIndex = MyBase.TabIndex
        _text.Visible = False
 
        AddHandler MyBase.ReadOnlyChanged, AddressOf Me.ReadOnlyChangedEventHandler
        MyBase.Controls.Add(_text)
        Me.Refresh()
 
    End Sub
    Protected Overrides Sub OnBackColorChanged(e As System.EventArgs)
        _text.BackColor = MyBase.BackColor
        MyBase.OnBackColorChanged(e)
        Me.Refresh()
    End Sub
    Protected Overrides Sub OnTabStopChanged(e As System.EventArgs)
        _text.TabStop = MyBase.TabStop
        MyBase.OnTabStopChanged(e)
        Me.Refresh()
    End Sub
    Protected Overrides Sub OnSizeChanged(e As System.EventArgs)
        _text.Size = MyBase.Size
        MyBase.OnSizeChanged(e)
        Me.Refresh()
    End Sub
    Protected Overrides Sub OnForeColorChanged(e As System.EventArgs)
        _text.ForeColor = MyBase.ForeColor
        MyBase.OnForeColorChanged(e)
        Me.Refresh()
    End Sub
    Protected Overrides Sub OnFontChanged(e As System.EventArgs)
        _text.Font = MyBase.Font
        MyBase.OnFontChanged(e)
        Me.Refresh()
    End Sub
    Protected Overrides Sub OnRightToLeftChanged(e As System.EventArgs)
        _text.RightToLeft = MyBase.RightToLeft
        MyBase.OnRightToLeftChanged(e)
        Me.Refresh()
    End Sub
    Protected Overrides Sub OnTabIndexChanged(e As System.EventArgs)
        _text.TabIndex = MyBase.TabIndex
        MyBase.OnTabIndexChanged(e)
        Me.Refresh()
    End Sub
    Protected Overrides Sub OnTextChanged(e As System.EventArgs)
        _text.Text = MyBase.Text
        If Me.ReadOnly = True Then
            If _DropDownWhenReadOnly = False Then
                _text.Show()
            End If
        End If
        MyBase.OnTextChanged(e)
        Me.Refresh()
    End Sub
 
    <Browsable(True), DefaultValue(True)> _
    Public Property DropDownWhenReadOnly As Boolean
        Get
            Return _DropDownWhenReadOnly
        End Get
        Set(value As Boolean)
            _DropDownWhenReadOnly = value
            If Me.ReadOnly = True Then
                If _DropDownWhenReadOnly = False Then
                    _text.Visible = True
                Else
                    _text.Visible = False
                End If
            Else
                _text.Visible = False
            End If
            Me.Refresh()
            RaiseEvent DropDownWhenReadOnlyChanged()
        End Set
    End Property
 

    Protected Shadows Sub ReadOnlyChangedEventHandler() 
        If Me.ReadOnly = True Then
            If _DropDownWhenReadOnly = False Then
                _text.Visible = True
            Else
                _text.Visible = False
            End If
        Else
            _text.Visible = False
        End If
        Me.Refresh()
    End Sub
 
End Class
 
 
Thank you very much for your good article.
GeneralProblem appears when style is ComboBoxStyle.DropDownList and toggling ReadOnlymemberjtxd17 Feb '11 - 19:58 
first we set DropDownStyle = ComboBoxStyle.DropDownList and ReadOnly = true, then we drop down the list and select one of the other item.
we can see the SelectedItem is not changed. But after setting ReadOnly = false, it changed...
GeneralRe: Problem appears when style is ComboBoxStyle.DropDownList and toggling ReadOnlymemberjtxd17 Feb '11 - 22:04 
after so much work i found a temporary solution.
 
add the followings to ReadOnly property 'set' part.
 
If (MyBase.IsHandleCreated) Then
    If (Me._SelectedIndex <> MyBase.SelectedIndex) Then
        SendMessage(Me.Handle, CB_SETCURSEL, Me._SelectedIndex, 0)
    End If
End If
 
but i don't know how to prevent the change after all...
QuestionA fix to your code when press AltGr and press down key with control set ReadOnly = truememberplaguebreath29 Jul '10 - 4:16 
 Protected Overrides Sub OnKeyDown(ByVal e As System.Windows.Forms.KeyEventArgs)
        If _ReadOnly Then
            If e.KeyCode = Keys.Up OrElse e.KeyCode = Keys.PageUp OrElse _
              e.KeyCode = Keys.PageDown OrElse _
             (e.KeyCode = Keys.Down And ((Control.ModifierKeys And Keys.Alt) <> Keys.Alt)) OrElse _
                (Control.ModifierKeys = (Keys.Control Or Keys.Alt)) Then
                e.Handled = True
            End If
        End If
        MyBase.OnKeyDown(e)
  End Sub
 
Hope that can help someone that found my same problem with bind Combo, plus another request.
After I put the control inside my Form and load it first time setting property to ReadOnly = true it's working, then I close the form, reopen it and the background appears white even if the ComboBox it's setted always as ReadOnly = true what can be the problem ? Thanks to everyone in advance and great control !
AnswerRe: A fix to your code when press AltGr and press down key with control set ReadOnly = truememberThomas Wells29 Jul '10 - 7:57 
I don't know what's going in with the control not appearing to be ReadOnly when it is. I'm guessing that when the control is being instantiated in the InitializeComponent routine the send message is useless because the control is not yet painted. I see that it does this both in the designer and at runtime. I was able to get it to work as expected at runtime by implementing ISupportInitialize and then in the EndInit routine sending the message again. Here's the code:
 
    Public Sub EndInit() Implements System.ComponentModel.ISupportInitialize.EndInit
        If _ReadOnly Then SendMessage(GetWindow(Me.Handle, GW_CHILD), EM_SETREADONLY, _ReadOnly, 0)
    End Sub

GeneralProblem with Strict set to On and browsable and defaultvaluemembermgardiner29 Aug '08 - 10:03 
when I started a small windows application and added xboxcombbox.vb as a class to my project it gave me 3 errors "Browsable, and Defaultvalue are not declared"
in section
 
"#Region "-- Properties --"
<Browsable(True), DefaultValue(False)> _
Public Property [ReadOnly]() As Boolean"
 
and
option strict on disallows implicit conversions from integer to boolean in section
 
SendMessage(Me.Handle, CB_SHOWDROPDOWN, System.Convert.ToInt32(False), 0)
any ideas?
GeneralRe: Problem with Strict set to On and browsable and defaultvaluememberThomas Wells29 Aug '08 - 10:24 
For the first error you need an import
 
Imports System.ComponentModel
 
For the second you could code this
 
SendMessage(Me.Handle, CB_SHOWDROPDOWN, CBool(System.Convert.ToInt32(False)), 0)

GeneralI do not understand...memberpbnec18 Aug '08 - 0:10 
Why I cannot define the property "SelectedIndex" like the following~
public new int SelectedIndex
{
get { if (mReadOnly){ return mSelectedIndex; } else { return base.SelectedIndex; } }
set
{
mSelectedIndex = value;
base.SelectedIndex = value;
if (value == -1)
{
	//Set it twice to work around databound bug KB327244
mSelectedIndex = value;
base.SelectedIndex = value;
}
}
}
And using the above code will make the property "SelectedIndex" have a wrong value when the field "mReadOnly" is equal to "true"...
 
But if I use the original definition of the property "SelectedIndex"~
public new int SelectedIndex
{
get { return mSelectedIndex; }
set
{
mSelectedIndex = value;
base.SelectedIndex = value;
if (value == -1)
{
	//Set it twice to work around databound bug KB327244
mSelectedIndex = value;
base.SelectedIndex = value;
}
}
}
The property "SelectedIndex" will have a wrong value when the field "mReadOnly" is equal to "false"...
GeneralRe: I do not understand...memberThomas Wells18 Aug '08 - 4:21 
I don't have a complete answer for you. The local mSelectedIndex variable is used to save the index value when the control is in a writable state (ReadOnly=False). When the control is ReadOnly if the listbox is dropped and a new value clicked, the base.SelectedIndex will change. I didn't want the value to appear changed when ReadOnly so that's why the override and the local variable. I would think that what you are experiencing is related to base.SelectedIndex changing when in a ReadOnly state but I don't see how. I don't have a working copy of the project in C#. It appears that the converted code in the post below is the same as my VB project. Take a look at your OnSelectedIndexChanged override to see if it's the same as the converted code.
 
HTH
 
Tom
AnswerRe: I do not understand...memberpbnec19 Aug '08 - 20:49 
I think that the problem is coming from the event "DropDownClosed". When the event handler is called, the property "SelectedIndex" in "xboXComboBox" does not reflect the current selection. However, the property "SelectedIndex" and "SelectedItem" in "ComboBox" reflect the current selection...
But I do not know how to correct the problem when I still want to use the event "DropDownClosed" (I know if I do NOT use this event, the property "SelectedIndex" in xboXComboBox MAY reflect the current selection).
QuestionIs it possible to copy the text when...memberpbnec24 Jul '08 - 18:29 
xboXComboBox.DropDownStyle == ComboBoxStyle.DropDownList && xboXComboBox.ReadOnly == false
???
If it is not possible, could you please made it possible? Because it lets user to choose the item from the drop down portion, and it lets user to copy the text from the text portion, but it does not let user to edit the text from the text portion
AnswerRe: Is it possible to copy the text when...memberThomas Wells25 Jul '08 - 10:07 
I seldom use the DropDownList style. You're right it does not let you type or copy. This mode is intended for simple selection only. It does respond to characters typed by moving to the letter in the list as you type. If I understand your request, it would add some of the functionality of the DropDown mode. DropDown mode allows typing in a value, even if it is not in the list. Using AutoComplete when in DropDown style would help by letting the user type more of the value but it still will allow any value to be entered. You would have to add some code to limit the value entered to the list. Checking the SelectedIndex in your application would let you know if the value entered was in the list. You could handle it that way.
Generalcreated C# version of your controlmemberMotero25 Apr '08 - 2:16 
cause the control was ment to fit into a C# dll, I translated your control into a C# control
 
here the code if someone may need it
 
using System;
using System.Windows.Forms;
using System.ComponentModel.Design;
using System.Runtime.InteropServices;
 
namespace myControls
{
public partial class ROComboBox : ComboBox
{
[DllImport("user32.dll")]
static extern IntPtr GetWindow(IntPtr hwnd, Int32 wCmd);
[DllImport("user32.dll")]
static extern Int32 SendMessage(IntPtr hwnd, Int32 wMsg, bool wParam, Int32 lParam);

private const Int32 GW_CHILD = 5;
private const Int32 EM_SETREADONLY = 0xCF;
private const Int32 EM_EMPTYUNDOBUFFER = 0xCD;
private const Int32 CB_SHOWDROPDOWN = 0x14F;
private bool mReadOnly = false;
private bool mDroppedDown;
private Int32 mSelectedIndex = -1;
private ComboBoxStyle mDropDownStyle = ComboBoxStyle.DropDown;
public bool ReadOnly
{
get
{ return mReadOnly; }
set
{
if (!DesignMode)
{
if (value)
{
if (mReadOnly != value)
{
mDropDownStyle = base.DropDownStyle;
base.DropDownStyle = ComboBoxStyle.DropDown;
}
}
else
{
base.DropDownStyle = mDropDownStyle;
}
}
mReadOnly = value;
base.TabStop = false;
base.SelectionLength = 0;
SendMessage(GetWindow(this.Handle, GW_CHILD), EM_SETREADONLY, value, 0);
SendMessage(GetWindow(this.Handle, GW_CHILD), EM_EMPTYUNDOBUFFER, value, 0);
mDroppedDown = false;
}
}
public new Int32 SelectedIndex
{
get { return mSelectedIndex; }
set
{
mSelectedIndex = value;
base.SelectedIndex = value;
if (value == -1)
{
mSelectedIndex = value;
base.SelectedIndex = value;
}
}
}

protected override void WndProc(ref Message m)
{
if (mReadOnly && mDroppedDown)
{
if (m.Msg == 273)
{
mDroppedDown = false;
SendMessage(this.Handle, CB_SHOWDROPDOWN, false, 0);
return;
}
}
base.WndProc(ref m);
}
 
protected override void OnSelectedIndexChanged(EventArgs e)
{
mSelectedIndex = base.SelectedIndex;
base.OnSelectedIndexChanged(e);
}
 
protected override void OnDropDown(EventArgs e)
{
mDroppedDown = true;
base.OnDropDown(e);
}
 
protected override void OnKeyDown(KeyEventArgs e)
{
if (mReadOnly)
{
if (e.KeyCode == Keys.Up || e.KeyCode == Keys.PageUp || e.KeyCode == Keys.PageDown ||
(e.KeyCode == Keys.Down & ((Control.ModifierKeys & Keys.Alt) != Keys.Alt)))
{
e.Handled = true;
}
}
base.OnKeyDown(e);
}
 
protected override void OnSelectionChangeCommitted(EventArgs e)
{
if (!mReadOnly)
{
base.OnSelectionChangeCommitted(e);
}
}
}
}

 
PS: great work! I love your combobox Smile | :)
GeneralRe: created C# version of your controlmemberThomas Wells25 Apr '08 - 3:28 
GREAT! Thanks for the conversion. I've done that same thing to fit C# code into my VB dll.
AnswerMy Inherited ComboBoxmemberpbnec18 Aug '08 - 15:59 
My inherited ComboBox has the property ReadOnly and the better event handling for the ReadOnly mode. See~
http://codeproject.com/KB/combobox/TdhComboBox.aspx?fid=333604&df=90&mpp=50&noise=1&sort=Position&view=Quick&select=2691672&fr=1[^]
Also see~
http://codeproject.com/KB/combobox/ComboBoxFiringEvents.aspx?fid=310212&df=90&mpp=50&noise=1&sort=Position&view=Quick&select=1644482[^]
GeneralRe: created C# version of your controlmemberFelix Todd3 Nov '08 - 12:52 
this version sets tabstop to false, instead of
base.TabStop = !mReadOnly;
as seems to be intended in the original code
minor change, I prefer not to change tabstop
nice work on this control Smile | :)
GeneralA small patch to handle setting the value of the ReadOnly propertymemberJeffrey Bradley16 Jan '08 - 1:27 
I noticed that if someone set the ReadOnly property to True twice then the original DropDownStyle was lost. This patch moves the checking code before the value is set and adds a check to see if the Value is different from the _ReadOnly variable.
 

 
    Public Property [ReadOnly]() As Boolean
        Get
            Return Me._ReadOnly
        End Get
        Set(ByVal Value As Boolean)
            ' If the DropDownStyle is anything other than DropDown then setting
            ' ReadOnly to true will have no affect. Therefore we'll force the style
            ' to DropDown as it goes to ReadOnly and restore it when it's turned off.
            If _ReadOnly <> Value AndAlso Value Then
                _DropDownStyle = MyBase.DropDownStyle
                MyBase.DropDownStyle = ComboBoxStyle.DropDown
            ElseIf Not Value Then
                MyBase.DropDownStyle = ComboBoxStyle.DropDownList ' _DropDownStyle
            End If
            ' Now set the private property to Value
            Me._ReadOnly = Value
            ' If readonly then don't let the user tab to the field
            MyBase.TabStop = Not Value
            ' Setting TabStop to false causes the text in the box to be selected if it matches
            ' an entry in the list. Setting selection length to zero removes the selection.
            MyBase.SelectionLength = 0
            ' Send the textbox portion of the combo the readonly message.
            ' It will change the color and behavior.
            SendMessage(GetWindow(Me.Handle, GW_CHILD), EM_SETREADONLY, Value, 0)
            ' If text was typed or pasted into the textbox, the context menu will
            ' have the undo activated. When the text box is in the readonly state
            ' the undo will still be active from the right click context menu
            ' allowing the user to restore the previous value. This sendmessage
            ' will clear the undo buffer which will clear the undo.
            SendMessage(GetWindow(Me.Handle, GW_CHILD), EM_EMPTYUNDOBUFFER, Value, 0)
            ' the dropdown may have been dropped before the readonly is set
            _DroppedDown = False
            Me.Refresh()
        End Set
    End Property

GeneralRe: A small patch to handle setting the value of the ReadOnly propertymemberThomas Wells24 Jan '08 - 3:10 
Thanks Jeffrey for the patch. I looked it over and incorporated it into my source. You had hard coded the DropDownList style when the box is set to not read only. I changed it to use the saved value so that the original setting would be restored.

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.130516.1 | Last Updated 18 Jun 2012
Article Copyright 2005 by Thomas Wells
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid