(Print this page)

Colors of disabled controls
Published date: Friday, July 1, 2005
On: Moer and Éric Moreau's web site

For many months, many of my users, the oldest in particular, were complaining that controls are hardly readable when these same controls are not enabled. Each time I was telling them that I was not able to do much about that since it is a normal behaviour of controls. Lately, a user complained that she couldn't read the content of disabled controls because of an eyes disease she now (sadly) have (she requires real contrast to be able to read and the grey color is not enough contrasting). That afternoon, I understood it was time for me to “Google” about this problem.

So what will this column be about? I will show you how to control colors of common controls such as TextBox, DateTimePicker, CheckBox, RadioButton, and ComboBox when you disable them. This will be done by inheriting controls and extending them.

Not much is built-in on that purpose

You are surely tempted to stop me right here to ask me questions like these:

Q: Why don’t you set the read-only property to True?

A: Only the TextBox control has this property. What will you do for other controls? Also, read-only textboxes are allowed to get focus, which is great in some cases, but you may want to avoid it under other circumstances.

Q: Why don’t you add all your controls to a frame like we use to do in the VB6 days and set the frame’s Enabled property to False?

A: Try it in VB.Net and you will rapidly discover that this good old trick isn’t working anymore. The entire container’s content is also disabled. Also, if your frame’s border is not visible or has no caption, users have no visual cues that controls inside it are disabled.

Q: Why don’t you simply change control’s BackColor and/or ForeColor properties after you disabled them?

A: Have you tried it? Changing the BackColor property is working but it isn’t for the ForeColor property. The main problem is that ForeColor that appears to pale (whatever the BackColor is).

Believe me. Not much can be done without some code from you. For some controls it will be very short. For others, it will be a bit longer.

Figure 1: Which side is easier to read?

So what’s left?

Google is your friend. That’s what I keep telling myself when I met a situation like this. After a couple of hours, I was a bit disappointed because nothing I found was working as expected. Roll your sleeves. We will extend the most fundamentals controls.

The creation of an interface

To implement the complete set of controls, I have created an interface (a class, not a GUI) that specifies only 2 properties:

Public Interface IDisabled

    Property BackColorDisabled() As System.Drawing.Color
    Property ForeColorDisabled() As System.Drawing.Color

End Interface
This interface will be implemented by all extender we will create after to ensure that they will all contains at least these 2 properties. If a class header contains the clause to implements this interface and the real code does not appear in this class, the project does not compile. You will see in the next example how the code is actually implemented.

Inheriting the original controls

I don’t want to recreate all these controls by myself. I only want to change one behaviour. To achieve this, I will create one extender class for each control I want to modify and inherit existing controls like this:

Public Class CheckBoxEx
    Inherits System.Windows.Forms.CheckBox

Implementing the interface

I have created an interface to ensure that all my controls will have the same 2 properties for the colors when the control is disabled. All these controls extender will have this code:

Implements IDisabled

Private mblnEnabled As Boolean = True
Private mclrBackColorDisabled As Color = Color.Gainsboro
Private mclrForeColorDisabled As Color = SystemColors.ControlText

Public Property BackColorDisabled() As System.Drawing.Color _
Implements IDisabled.BackColorDisabled
    Get
        Return mclrBackColorDisabled
    End Get
    Set(ByVal Value As System.Drawing.Color)
        mclrBackColorDisabled = Value
    End Set
End Property

Public Property ForeColorDisabled() As System.Drawing.Color _
Implements IDisabled.ForeColorDisabled
    Get
        Return mclrForeColorDisabled
    End Get
    Set(ByVal Value As System.Drawing.Color)
        mclrForeColorDisabled = Value
    End Set
End Property
It would have been great to have written the code elsewhere and have inherited it but since the .Net Framework supports only single inheritance we sometime have to use interfaces to achieve behaviours like this one.

If you never use or read about interface implementation, I strongly suggest you do.

Overriding the OnEnabledChanged event

For almost all controls, I will override the OnEnabledChanged event. This event is raised when you change the value of the Enabled property of the control. This is exactly what we want to do. We want to change the behaviour when the controls are disabled. When overriding an event like this one in a derived class, be sure to call the base class's OnEnabledChanged method so that registered delegates also receive the event.

CheckBox and RadioButton

These are probably the 2 easiest controls to modify. Both are implemented the same way. Thanks to the AutoCheck property of these 2 controls, code is fairly easy. When this property is set to false, clicking the controls has no effect unless you handle it yourself in the Click event.

Only the Enabled property needs to be shadowed like this:

Public Shadows Property Enabled() As Boolean
    Get
        Return mblnEnabled
    End Get
    Set(ByVal Value As Boolean)
        'Keeps the current Back/Fore color
        Static sclrBackColor As System.Drawing.Color
        Static sclrForeColor As System.Drawing.Color
        Static sblnTabStop As Boolean

        If sclrBackColor.IsEmpty Then
            sclrBackColor = Me.BackColor
            sclrForeColor = Me.ForeColor
            sblnTabStop = Me.TabStop
        End If

        'Stop looping in itself
        Static sblnIsProcessing As Boolean
        If sblnIsProcessing Then Exit Property
        sblnIsProcessing = True

        mblnEnabled = Value
        Me.Enabled = True
        If mblnEnabled Then
            Me.BackColor = sclrBackColor
            Me.ForeColor = sclrForeColor
            Me.AutoCheck = True
            Me.TabStop = sblnTabStop
        Else
            Me.BackColor = Me.BackColorDisabled
            Me.ForeColor = Me.ForeColorDisabled
            Me.AutoCheck = False
            Me.TabStop = False
        End If
        Me.Invalidate()

        sblnIsProcessing = False
    End Set
End Property

In this code, we have to remember originals color (Back and Fore) to reset them correctly and I also have some code to stop this event looping in itself when this same property is set to True (this way the control is not greyed out).

That’s it for these 2 controls. The controls will appear with different colors (if you don’t set the same colors for both Color and ColorDisabled) and the even if the control is still Enabled, there will be no effect clicking on them.

TextBox

It is a bit more complex here. First, I override the OnEnabledChanged event. The SetStyle method is used to tell call our OnPaint event.

Protected Overrides Sub OnEnabledChanged(ByVal e As System.EventArgs)
    MyBase.OnEnabledChanged(e)
    If Not Me.Enabled Then
        Me.SetStyle(ControlStyles.UserPaint, True)
    Else
        Me.SetStyle(ControlStyles.UserPaint, False)
    End If

    Me.Invalidate()
End Sub
When the control needs to be painted, this event is called. Because we have to redraw the content of the TextBox (using DrawString) we need to take care of the alignment. We also take care of filling the background of the control using the appropriate color.
Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
    MyBase.OnPaint(e) 
    Dim TextBrush As SolidBrush
    Dim sf As New StringFormat

    Select Case Me.TextAlign
        Case HorizontalAlignment.Center
            sf.Alignment = StringAlignment.Center
        Case HorizontalAlignment.Left
            sf.Alignment = StringAlignment.Near
        Case HorizontalAlignment.Right
            sf.Alignment = StringAlignment.Far
    End Select

    Dim rDraw As RectangleF = RectangleF.op_Implicit(Me.ClientRectangle)
    rDraw.Inflate(0, 0)

    If Me.Enabled Then
        TextBrush = New SolidBrush(Me.ForeColor)
    Else
        TextBrush = New SolidBrush(Me.ForeColorDisabled)
        Dim BackBrush As New SolidBrush(Me.BackColorDisabled)
        e.Graphics.FillRectangle(BackBrush, 0.0F, 0.0F, Me.Width, Me.Height)
    End If
    e.Graphics.DrawString(Me.Text, Me.Font, TextBrush, rDraw, sf)
End Sub
That’s it.

DateTimePicker

The same override I did for the TextBox has to be done here too.

The OnPaint event is a bit shorter because there are fewer properties to take care here:

Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
    MyBase.OnPaint(e)
    Dim TextBrush As SolidBrush

    'Set user selected backcolor when disabled
    If Me.Enabled Then
        TextBrush = New SolidBrush(Me.ForeColor)
    Else
        TextBrush = New SolidBrush(Me.ForeColorDisabled)
        Dim BackBrush As New SolidBrush(Me.BackColorDisabled)
        e.Graphics.FillRectangle(BackBrush, ClientRectangle)
    End If

    'Paint text
    e.Graphics.DrawString(Me.Text, Me.Font, TextBrush, 0.0F, 0.0F)
End Sub
So another one is ready!

ComboBox

The ComboBox is the last control I wanted to specialize. It wasn’t the easiest! The tricks I have used for the TextBox and the DateTimePicker controls were not working here. The ComboBox control is already a construct of multiple controls (a TextBox zone, a button, …) and isn’t reacting correctly.

Instead, I have used much the same technique I have use for the CheckBox. I have shadowed the Enabled property like this:

Public Shadows Property Enabled() As Boolean
    Get
        Return mblnEnabled
    End Get
    Set(ByVal Value As Boolean)
        'Keeps the current Back/Fore color
        Static sclrBackColor As System.Drawing.Color
        Static sclrForeColor As System.Drawing.Color
        Static sobjComboBoxStyle As ComboBoxStyle
        Static sblnTabStop As Boolean

        If sclrBackColor.IsEmpty Then
            sclrBackColor = Me.BackColor
            sclrForeColor = Me.ForeColor
            sobjComboBoxStyle = Me.DropDownStyle
            sblnTabStop = Me.TabStop
        End If

        'Stop looping in itself
        Static sblnIsProcessing As Boolean
        If sblnIsProcessing Then Exit Property
        sblnIsProcessing = True

        mblnEnabled = Value
        Me.Enabled = True
        If mblnEnabled Then
            Me.BackColor = sclrBackColor
            Me.ForeColor = sclrForeColor
            Me.DropDownStyle = sobjComboBoxStyle
            Me.ContextMenu = Nothing
            Me.TabStop = sblnTabStop
        Else
            Me.BackColor = Me.BackColorDisabled
            Me.ForeColor = Me.ForeColorDisabled
            Me.DropDownStyle = ComboBoxStyle.Simple
            Me.Height = 21
            Me.ContextMenu = mmnuEmpty
            Me.TabStop = False
        End If
        Me.Invalidate()

        sblnIsProcessing = False
    End Set
End Property

I also add to override 2 events to disallow users to type in the control and have the drop down portion appears:
Protected Overrides Sub OnKeyDown(ByVal e As System.Windows.Forms.KeyEventArgs)
    If Not Me.mblnEnabled AndAlso _
        (e.KeyCode = Keys.Up OrElse _
         e.KeyCode = Keys.Down OrElse _
         e.KeyCode = Keys.Delete) Then
        e.Handled = True
    Else
        MyBase.OnKeyDown(e)
    End If
End Sub

Protected Overrides Sub OnKeyPress(ByVal e As System.Windows.Forms.KeyPressEventArgs)
    If Not Me.mblnEnabled Then
        e.Handled = True
    Else
        MyBase.OnKeyPress(e)
    End If
End Sub
And that’s it!

Testing the whole thing

In the real world, all the code I have written up to here would be placed into a Windows Control library project (or a Class library project). This way, you can easily add your new extended controls to your toolbox to drop them on a form. Do you remember that I have written a column on that topic a long time ago? See Creating your own Windows Custom Control in the March 2003 edition of the UTMag.

Figure 2: The Toolbox

You use these controls just like the real ones except that they have 2 new properties (the colors to use when disabled. Isn’t it fantastic?

Conclusion

It’s not that hard after all but I couldn’t find all that complete info in a single spot like I am giving you today.

I’m pretty sure your users will really appreciate this like mine did!

I hope you appreciated the topic and see you next month.


(Print this page)