VB.NET Tips / Tricks / Examples and Help

VB.NET : Extended Combobox to better visually handle being disabled

If you do any WinForms programming then chances are you have used comboboxes, and perhaps at one point or another, you disabled one of those comboboxes because you did not want the user to be able to make a selection. This works fine, however I actually was getting some complaints from users of some of my software apps saying it was too hard to read the disabled text in a combo. Sometimes just because a feature is disabled, does not mean that the data being displayed in it is of no importance.

So today lets see how we can change this standard functionality to make things a bit easier on our end users.
We will use Visual Basic .NET to extended the standard WinForms .NET combobox control.

Lets start by taking a look at the demo. Here we see 2 disabled comboboxes. The top one is a standard WinForm combobox, which has a gray on gray appearance, and can be pretty hard to read. This demo uses larger fonts, so at a smaller font size, it becomes even more difficult. The second one is the extended ComboBox, and you can see that the ForeColor has stayed black, and the BackColor is gray indicating it is disabled. Much easier to read. So lets see what the code looks like to accomplish this.

 

Like all the extended controls you can make from the standard WinForm controls, we need to inherit from the control we want to extend, in this case Combobox.

The real trick here is that we are never going to actually make the control disabled. We are simply going to keep track of if it SHOULD be disabled, and stop certain actions from happening in that case. Those actions would be the ability to drop down the combo to make a selection, or to type in the textbox portion of the combo. Take those away, and the combobox is essentially disabled. The other thing we need to take into account, is the default context menu, which gives the user access to features like paste.

In order to do this, we setup a few private fields to keep track of the disabled/enabled state manually.

Here is the start of our class

' 2008 - ZerosAndTheOne.com

' Matthew Kleinwaks

 

'Extended Combobox Control for better

'handling of disabled appearance

 

Option Strict On

Option Explicit On

 

Imports System.Windows.Forms

 

Public Class ComboboxEx

    Inherits ComboBox

 

 

#Region "Private Backing Fields"

    ''' <summary>

    ''' Private backing field for the shadowed enabled property

    ''' </summary>

    Private _Enabled As Boolean = True

 

    ''' <summary>

    ''' BackColor of control when it is enabled

    ''' </summary>

    Private _enabledBackcolor As Color = MyBase.BackColor

 

    ''' <summary>

    ''' BackColor of a control when it is disabled

    ''' </summary>

    ''' <remarks></remarks>

    Private _disabledBackColor As Color = Color.LightGray

#End Region

Note it is just 3 private fields we will use. One to keep track of the enabled state, and 2 to keep track of the BackColor, depending on the enabled state. The reason we use these 2 BackColor fields, is because we set the controls underlying BackColor property to one of these when the enabled state changes, so we need to keep track of what each color should be when enabled does change.

Next lets define our public fields that will be exposed to the consumer of this control.

#Region "Public Exposed Fields"

 

    ''' <summary>

    ''' Gets or Sets the value indicating if this control is enabled

    ''' </summary>

    ''' <value>Boolean</value>

    ''' <returns>True of control is enabled, otherwise false</returns>

    ''' <remarks>This property shadows the ComboBox base class enabled property</remarks>

    Public Shadows Property Enabled() As Boolean

        Get

            Return _Enabled

        End Get

        Set(ByVal Value As Boolean)

            If _Enabled <> Value Then

                _Enabled = Value

                OnEnabledChanged(New EventArgs)

            End If

        End Set

    End Property

 

    ''' <summary>

    ''' Gets or Sets the BackColor of the control when it is disabled

    ''' </summary>

    ''' <value>Color Structure</value>

    Public Property DisabledBackColor() As Color

        Get

            Return _disabledBackColor

        End Get

        Set(ByVal value As Color)

            _disabledBackColor = value

            If Not _Enabled Then

                MyBase.BackColor = _disabledBackColor

            End If

        End Set

    End Property

 

    ''' <summary>

    ''' Gets or Sets the BackColor of the control when it is enabled

    ''' </summary>

    ''' <value>Color Structure</value>

    ''' <remarks>Shadows the base class BackColor property</remarks>

    Public Shadows Property BackColor() As Color

        Get

            Return _enabledBackcolor

        End Get

        Set(ByVal value As Color)

            If _enabledBackcolor <> value Then

                _enabledBackcolor = value

                MyBase.BackColor = _enabledBackcolor

            End If

        End Set

    End Property

 

#End Region

 

So just like we have 3 backing fields, we have 3 public properties. The biggest thing to note is that 2 of these properties (BackColor and Enabled) are declared shadows. This means they hide and replace the functionality that the base Combobox class provided for these given properties. It is now totally up to us to handle these properties in our new class.

To do the magic here, we will override a few protected methods from the combobox base class, and add our custom functionality.

Lets start with OnEnabledChanged. This is the method that is called when Enabled is changed. In the Enabled public property we defined above, we call this method.

    ''' <summary>

    ''' When the shadowed enabled property changes, this method is called

    ''' </summary>

    Protected Overrides Sub OnEnabledChanged(ByVal e As System.EventArgs)

 

        'COMMON ROUTINE FOR TOGGLING ENABLED STATUS

        ToggleEnabled()

 

        'SEND NOTIFICATION TO BASE CLASS

        MyBase.OnEnabledChanged(e)

 

    End Sub

Pretty simple (we will define the ToggleEnabled method after we define all our overrides).

Next is the PreProcessMessage override. We use this to look at key presses and decide if we want to throw them out, or let them pass to the control to do whatever the given keypress might do. Basically in this example, if the control is enabled, we don't do anything at all, so that is the first thing we check. Next we allow through key presses like Tab, Left, and Right. You could customize what you allow based on the needs of your program.

    'OVERRIDE PreProcessMessage TO LOOK FOR KEY PRESSES AND FILTER THEM

    Public Overrides Function PreProcessMessage(ByRef msg As Message) As Boolean

 

        'PREVENT KEYBOARD ENTRY IF CONTROL IS DISABLED

        If Not _Enabled Then

 

            'CHECK IF ITS A KEYDOWN MESSAGE (&H100)

            If msg.Msg = &H100 Then

 

                'GET THE KEY THAT WAS PRESSED

                Dim key As Int32 = msg.WParam.ToInt32

 

                'ALLOW TAB, LEFT, OR RIGHT KEYS

                If key <> Keys.Tab OrElse _

                   key <> Keys.Left OrElse _

                   key <> Keys.Right Then

 

                    Return True

 

                End If

            End If

        End If

 

        'CALL BASE METHOD SO DELEGATES RECEIVE EVENT

        Return MyBase.PreProcessMessage(msg)

 

    End Function

 

Next we override the WndProc method. In here we are looking for 2 possible messages, which have to deal with the drop down of the list in the combobox. We don't want that to happen when the control is disabled, so we filter out those specific messages, and allow all other messages to go through as normal.

    'OVERRIDE WndProc TO LOOK FOR DROP DOWN MESSAGES AND FILTER THEM

    Protected Overrides Sub WndProc(ByRef m As Message)

 

        'PREVENT DROPDOWN LIST DISPLAYING IF READONLY

        If Not _Enabled Then

            If m.Msg = &H201 OrElse m.Msg = &H203 Then

                Return

            End If

        End If

 

        'CALL BASE METHOD SO DELEGATES RECEIVE EVENT

        MyBase.WndProc(m)

    End Sub

 

Finally, the last override we need to do, is the OnParentEnabledChanged method. This is because if the control is in a container, like a groupbox or panel, and you set the container's enabled property to false, you would expect the combobox to also have it's enabled property set to false. However, the parent control does not simply loop its child controls and set their enabled properties. It actually calls the OnParentEnabledChanged for each child, and lets the child handle what it want's to do in that case. If the control is enabled, we pass through the message like normal. If it is disabled, then we call the ToggleEnabled routine, which is our last routine to define.

    'WHEN THE CONTROL IS IN A CONTAINER, AND THE CONTAINER'S ENABLED PROPERTY

    'IS SET TO FALSE, THIS CONTROL GETS ITS OnParentEnabledChanged CALLED

    'NOT JUST THE CONTROLS ENABLED PROPERTY SET SO WE OVERRIDE THIS, AND

    'TOGGLE ENABLED STATE ACCORDINGLY

    Protected Overrides Sub OnParentEnabledChanged(ByVal e As System.EventArgs)

 

        _Enabled = MyBase.Parent.Enabled

 

        If _Enabled Then

            MyBase.OnParentEnabledChanged(e)

        Else

            ToggleEnabled()

        End If

    End Sub

 

So it is time to define our ToggleEnabled routine. It is actually pretty simple. We set tabstop to false if the control is disabled, and true if it is enabled, so users won't set focus to it with the tab key. We also set the ContextMenuStrip equal to a New ContextMenuStrip. This actually causes NO menu to appear when the control is right clicked, since the NEW context menu we make has no menu items on it. Setting this property to nothing will bring back the standard cut/copy/paste context menu. Keep this all in mind if you happen to use a custom context menu on this control. We also set the BackColor property to our desired colors were, and also set the SelectionLength to 0. This just deselects any selected text, and is really optional and up to you.

#Region "Support Methods"

    'COMMON ROUTINE FOR TOGGLING ENABLED STATE

    Private Sub ToggleEnabled()

 

        'IF THE CONTROL IS DISABLED, TURN OFF ITS TABSTOP

        MyBase.TabStop = _Enabled

 

        'IF THE CONTROL IS DISABLED, SET ITS CONTEXT MENU TO

        'A DUMMY NEW CONTEXT MENU SO WE DON'T GET THE

        'DEFAULT CONTEXT MENU, OTHERWISE SETTING IT TO

        'NOTHING REAPPLIES THE DEFAULT CONTEXT MENU

        'ALSO SET BACK COLOR ACCORDINGLY

        If Not _Enabled Then

            MyBase.ContextMenuStrip = New ContextMenuStrip

            MyBase.BackColor = _disabledBackColor

        Else

            MyBase.ContextMenuStrip = Nothing

            MyBase.BackColor = _enabledBackcolor

        End If

 

        'DESELECT ANY TEXT FROM COMBOBOX

        Me.SelectionLength = 0

 

    End Sub

#End Region

 

So that all there is to it. We end up with these results:

 

Attached is the source code for this project in Visual Studio 2008 format, targetting .NET 2.0

 

As always, please leave your comments and suggestions.


Posted May 12 2008, 10:51 PM by Matthew Kleinwaks

Comments

gw wrote re: VB.NET : Extended Combobox to better visually handle being disabled
on Sat, Jun 28 2008 6:34 PM

Nice workaround, but the dropdown button still acts enabled (color, mouseover color) when in disabled mode.  In c#, couldn't get the "new" keword to work on the Enabled property (vs VB Shadows) in such a way that the Set method would get called.  Ended up just using the class' base Enabled property since the OnEnabledChanged was overridden (don't call base.OnEnabledChanged).

Matthew Kleinwaks wrote re: VB.NET : Extended Combobox to better visually handle being disabled
on Sun, Jun 29 2008 10:06 AM

For any methods you want to handle the disabled functionality, you would just override them to act how you want in the case where the control should be disabled.

As for the C# New versus the VB shadows, I think this may be one of those cases where VB has an upper hand with the way it works and the shadows keyword. I will mess around with it in C# and see about getting this example working.

Matthew Kleinwaks wrote re: VB.NET : Extended Combobox to better visually handle being disabled
on Mon, Jun 30 2008 4:08 PM

gw,

after reading your comment again, i realized you were talking about the dropdown button to drop down the list (I should have read it more clearly the first time).

Yeah that was one of the drawbacks to this workaround. I never really looked too hard at a solution for that, but there may or may not be an easy one to implement.

I was able to at least mimic the behavior of the VB example in C# though. What was your specific issue with using new versus shadows?

Edneeis wrote re: VB.NET : Extended Combobox to better visually handle being disabled
on Tue, Jan 20 2009 11:16 PM

What are the extra two buttons in the Title bar (by the Min/Max and Close buttons)?

Matthew Kleinwaks wrote re: VB.NET : Extended Combobox to better visually handle being disabled
on Mon, Feb 2 2009 1:42 PM

Those 2 buttons are expand to all monitors, and move to other monitor. They are a feature of a program called UltraMon which is a tool that picks up where Windows lacks in support for multiple monitors. Since I run 4 LCDs on my work computer, it is very useful. They show up on all Win32 windows. The program has other nice features, so if you use more than one monitor, you should check it out.

software development company wrote re: VB.NET : Extended Combobox to better visually handle being disabled
on Fri, Aug 21 2009 11:46 AM

That  was an inspiring post,

Keep up the good work

Bib34690 wrote re: VB.NET : Extended Combobox to better visually handle being disabled
on Wed, Sep 16 2009 1:39 PM

Super...

plus fort que Crosoft.

merci.



© 2014 - ZerosAndTheOne.com - Hosting by Orcsweb (http://www.orcsweb.com/)
Powered by Community Server (Non-Commercial Edition), by Telligent Systems