Introduction
The ColoredErrorProvider
is a custom Extender Provider. Like the ErrorProvider
component, this class can be used to warn the user of invalid data.
Extender Providers
An extender provider allows you to extend a control without creating additional classes.
ToolTip Extender Provider
The ToolTip
control in .NET is an extender provider. Instead of each of the controls like buttons, labels, textboxes etc. having a ToolTip
property, we can use the ToolTip
class to set a tooltip for it as shown below:
ToolTip toolTip1 = new ToolTip();
toolTip1.SetToolTip(this.label1, "This is a label");
toolTip1.SetToolTip(this.button1, "this is a button");
ErrorProvider Extender Provider
The ErrorProvider
is a pretty neat control that allows you to display an error icon next to controls if the data in them is invalid. This is very convenient considering that the alternative would be to:
- pop up an error dialog
- show a status bar error
- allow verification to be handled during processing.
The class in this example is similar to the ErrorProvider
class. What has been changed is that the error/warning is indicated to the user by flashing the control's background color. I wrote this because the icon I had to display using ErrorProvider
could not be placed in any convenient location; the icon overlapped with some other control or went out of the bounds of the form. And of course, to learn how to write an Extender Provider.
Sample
The sample contains a small program with a few components that illustrate using this class. Clicking the "Toggle Error" button sets/unsets the errors on the controls in the groupbox. TextBox3
is validated. If it contains more than 10 chars, the error is set. Change the number of characters in this text field and take focus away from it to see the effect of the ColoredErrorProvider
class.
Usage
While creating the ColoredErrorProvider
, one can specify:
blinkCount
: The number of times the control will blink before it settles on the settle color.
blinkInterval
: The time in milliseconds between each blink.
While setting the error, one can specify:
control
: The Control
on which the error must be applied.
value
: If it is not empty then the control will blink. If it is empty then it will not display the error. The string itself is NOT displayed to the user like ErrorProvider
does.
blinkColor
: The color that is the background color when the control blinks.
blinkCount
: The number of times the control will blink before it settles on the settle color.
settleColor
: The background color of the control when the blinking is over.
Code Walkthrough
Class Form1
is the example application form. I will not be walking you through this code.
The ColoredErrorProvider
extends a Control
and implements IExtenderProvider
which is necessary to implement your own extender provider. MSDN says "Any component that provides extender properties must implement IExtenderProvider
. A visual designer can then call CanExtend
to determine which objects in a container should receive the extender properties". Since all derivatives of the Control
class will have a BackColor
property, we override the CanExtend
method to return true
if the object is a Control
:
bool IExtenderProvider.CanExtend(object target)
{
if (target is Control)
{
return true;
}
return false;
}
In order to retain the different controls for which a ColoredError
has been provided, I instantiated a HashTable
. The key to the HashTable
is the control and the value is a BlinkControlDetails
structure that contains the details of the ColoredError
associated with the control.
private Hashtable originalControlColors = new Hashtable();
struct BlinkControlDetails
{
public Color OriginalBackColor;
public Color BlinkBackColor;
public Color SettleBackColor;
public string ErrorString;
private int[] BlinkCountArray;
...
}
The BlinkCountArray
is a hack workaround. I didn't know how to change the value of a primitive type that is a member of a structure and which is returned as a key's value from the HashTable
. If somebody would enlighten me about a better way to do this, it would be great. The BlinkCountArray
contains only one entry. This is indirectly manipulated through the public property BlinkCount
.
When SetError
is called on the ColoredErrorProvider
:
- I check if the error is to be set or removed depending on the length. If it is to be removed then set the background color to the original color and then remove it from the
HashTable
.
- If it is new, I create a new
BlinkControlDetails
with appropriate entries and add it to the HashTable
. We also enable the timer so that we can make the control's back color blink.
public void SetError(Control control,
string value, Color blinkColor, int blinkCount, Color settleColor)
{
if (value == null)
{
value = string.Empty;
}
if (value.Length == 0)
{
if (this.originalControlColors.ContainsKey(control))
{
control.BackColor = ((BlinkControlDetails)
this.originalControlColors[control]).OriginalBackColor;
this.originalControlColors.Remove(control);
}
control.Invalidate();
}
else
{
BlinkControlDetails bcd = new BlinkControlDetails(value,
control.BackColor, blinkColor, settleColor, blinkCount*2-1);
this.originalControlColors.Add(control, bcd);
control.BackColor = blinkColor;
control.Invalidate();
if (!this.timer.Enabled)
{
this.timer.Enabled = true;
}
}
}
When the class is created, I create an instance of a timer. The timer is enabled when SetError
is called.
private Timer timer = new Timer();
When the timer ticks, it calls the method OnTimerTick
.
private void OnTimerTick(Object myObject, EventArgs myEventArgs)
{
bool atLeastOneBlinked = false;
foreach (Control c in this.originalControlColors.Keys)
{
BlinkControlDetails bcd =
(BlinkControlDetails)this.originalControlColors[c];
if (bcd.BlinkCount>=1)
{
if (c.BackColor.Equals(bcd.OriginalBackColor))
{
c.BackColor = bcd.BlinkBackColor;
}
else if (c.BackColor.Equals(bcd.BlinkBackColor))
{
c.BackColor = bcd.OriginalBackColor;
}
bcd.BlinkCount--;
atLeastOneBlinked = true;
}
else if(bcd.BlinkCount==0)
{
c.BackColor = bcd.SettleBackColor;
bcd.BlinkCount--;
atLeastOneBlinked = true;
}
else
{
continue;
}
c.Invalidate();
}
if (!atLeastOneBlinked)
{
this.timer.Enabled = false;
}
}
We loop through the controls in the HashTable
which contains the controls onto which the error provider has been set. For each control, we flip the back color between the original back color and the blink back color. On each blink, we decrement the blink count. Once blink count has reached 0, we set the back color to the settleColor
. The settle color will inform the user that there is a problem without disturbing him/her with the blinking.
Variable atLeastOneBlinked
is set to true
if there is at least one more control that has to blink. If there isn't, we can disable the timer.
Related Links