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
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
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
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
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
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
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
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
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.