rom the beginning, the Tag property has been a Visual Basic programming staple. Any time you want to attach a little extra bit of information, you can put it in the Tag property. In .NET, the Tag property became even more useful, allowing you to store objects rather than just strings.
While Tag is great, there are times when it’s just not quite enough. What if you’re working on a large project and some other developer needs to use the same Tag property for something else? You could store a collection in the Tag property where you can both put values but that requires extra coordination. Or you could subclass the control to add an extra property to it, but that would give you a whole new class to debug and maintain. Wouldn’t it be nice if you could just add an extra Tag property to a control for your own private use?
With ExtenderProviders you can! An ExtenderProvider is a component that adds any sort of extra service to existing controls and components. The ErrorProvider, HelpProvider, and ToolTip components are all provider classes that come with Visual Studio. When you add a ToolTip component to a form, for example, it provides a new ToolTip property to all the controls on the form. If the ToolTip component is named ToolTip1, then you’ll see that every control on the form magically acquires a property?a new property called ToolTip on ToolTip1. This name means that the ToolTip1 component is providing a ToolTip property.
It’s not too hard to add your own ExtenderProviders to add extra properties and behaviors to existing controls.
The ExtraTag Property
The ExtraTagProvider class provides an ExtraTag property for Components. This class performs three tasks:
- It indicates objects for which it may provide a property.
- It sets an object’s property value.
- It retrieves an object’s property value.
Listing 1 shows how the ExtraTagProvider class handles these chores. This code is in Visual Basic but you can also download a C# version.
The class is decorated with a ProvideProperty attribute that indicates the name of the property that the class will provide: ExtraTag. The second parameter to the attribute indicates that the provider will serve Components, although the CanExtend function described shortly can refine that selection to indicate specific kinds of components that the class can extend .
The ExtraTagProvider class inherits from Component, so it appears in the Toolbox and can be placed on a form. It implements the IExtenderProvider interface.
IExtenderProvider requires a CanExtend function that takes an object as a parameter and returns True if the ExtenderProvider can serve that object. In this example, CanExtend returns True if the object is any kind of Component, but you could make this function much more specific. For example, you could make an ExtenderProvider that works for TextBoxes and Labels but not other controls. Or, you could make it work only for horizontal and vertical scroll bars and track bars, which all have similar Minimum, Maximum, LargeChange, SmallChange, and Value properties.
ExtraTagProvider must have some sort of data structure for storing the ExtraTag values for the Components it extends. This example uses a Dictionary instance named m_ExtraTags to store those values. The dictionary’s keys are Components and its values are Strings. For a Component named C, m_ExtraTags(C) is the Component’s ExtraTag value.
The GetExtraTag function returns a Componen’s ExtraTag value. This function is decorated with the ExtenderProvidedProperty attribute. If the m_ExtraTags dictionary contains a value for this Component, the function returns it. If the dictionary doesn’t contain an entry for this Component, the function returns an empty string.
The SetExtraTag subroutine saves a new ExtraTag value for a Component. It first removes the Component’s entry in the m_ExtraTags dictionary if one is present. Then, if the new ExtraTag value is non-blank, it adds the new value to the dictionary.
Now you can avoid fighting with other developers over who has control of the Tag property. Add an ExtraTagProvider to a form and all of the form’s Components get a new ExtraTag property that you can use while other, less enlightened developers wrangle over control of Tag.
In fact, other developers can also use an ExtraTagProvider to store their own information. If someone adds another ExtraTagProvider named ExtraTagProvider2 to the form, then every Component on the form gets another property named ExtraTag on ExtraTagProvider2 and each of you can use your ExtraTag properties without interfering with the other.
The form designer displays this property for components as ExtraTag on ExtraTagProvider1 and you can use the Properties window to set its value. However, ExtraTag is an extension provided by the ExtraTagProvider class, and is not truly a property of the components. That means your program’s code can’t set a component’s ExtraTag property directly. Instead it must call the ExtraTagProvider’s SetExtraTag method as shown in the following code.
ExtraTagProvider1.SetExtraTag(txtFirstName, "New value.")
The ExtraTagProvider is handy on occasion, but it’s really just another sneaky way to associate data with components. ExtenderProviders can do much more.
Because an ExtenderProvider works with instances of components, it can do all sorts of things to those components. For example, it can modify the components’ properties and catch their events.
The FocusStatusLabelProvider (available for download in both Visual Basic and C# forms) is derived from the Label class. It is also an ExtenderProvider that provides the StatusMessage property. When an extended control receives focus, the FocusStatusLabelProvider displays the control’s StatusMessage value. You can use this control to give users hints about what to enter in a field.
Much of the code that makes the class an ExtenderProvider, and that saves and restores properties, is similar to the code used by the ExtraTagProvider, but one difference is that FocusStatusLabelProvider inherits from the Label control instead of from Component.
A more interesting difference is that the SetStatusMessage subroutine adds and removes event handlers for the extended control. The following code shows how the SetStatusMessage subroutine works. If the m_StatusMessages dictionary contains a value for the extended control, the code removes its entry and any event handlers assigned to the control’s GotFocus and LostFocus events. Then, if the new status message is non-blank, the routine adds it to the dictionary and attaches event handlers to the control’s GotFocus and and LostFocus events. When an extendee receives focus, the provider sets its own text to display the extendee’s status message. When an extendee loses focus, the provider clears its text:
Private Sub Extendee_GotFocus(ByVal sender As Object, _ ByVal e As System.EventArgs) Me.Text = GetStatusMessage(sender)End SubPrivate Sub Extendee_LostFocus(ByVal sender As Object, _ ByVal e As System.EventArgs) Me.Text = ""End Sub
Figure 1 shows the UseFocusStatusLabelProvider application demonstrating this provider. In Figure 1, the form’s focus is in the Street TextBox so the provider (docked to the bottom edge of the form) is displaying instructions for the Street field.
|Figure 1. FocusStatusLabelProvider: This class displays status information when a control has focus.|
A Missing Provider
The FocusStatusLabelProvider class catches its extendees’ object’s GotFocus and LostFocus events and displays appropriate messages. The MissingBackColorProvider class also catches extendees’ events and takes action.
This class catches a TextBox’s Validating event and determines whether the control contains text. If the control is blank, the provider saves the TextBox’s current BackColor value in its m_BackColor dictionary and then sets the control’s BackColor to the value stored in the extender’s MissingBackColor property. If the TextBox contains text, the provider restores its original BackColor.
The MissingBackColorProvider makes it easy to flag a required field with a colored background if the user leaves it blank (download the Visual Basic and C# programs that demonstrate this provider here.)
The IntegerRangeProvider is also useful for validating user values entered into TextBoxes. This extender provides two properties: MinValue and MaxValue. It catches a TextBox’s Validating event and determines whether the TextBox contains an integer value between MinValue and MaxValue. If the value is not an integer or falls outside of this range, IntegerRangeProvider displays an ErrorProvider with an appropriate message.
Figure 2 shows an IntegerRangeProvider that validates three TextBoxes. The first TextBox contains a value that is within its allowed range, but the second and third TextBoxes hold values that are out of bounds. In Figure 2, the mouse is hovering over the second TextBox’s ErrorProvider so its message is visible.
|Figure 2. IntegerRangeProvider: This class uses an ErrorProvider component to display error messages if a TextBox’s value is out of bounds.|
The main difference between IntegerRangeProvider and the previous providers is that it uses an ErrorProvider to display its messages. IntegerRangeProvider inherits from Component so you can put one on a form. If you double-click a component in the Solution Explorer, Visual Studio opens it in a graphic designer. You can then drag tools from the Toolbox and drop them on this designer to add them to the Component.
When you drag a tool onto the Component, Visual Studio adds appropriate code to the Component to support the tool. The following code shows how Visual Studio supports the ErrorProvider added to the IntegerRangeProvider. The code creates a container for the provider’s Components and makes an InitializeComponent method to create the container, it builds the ErrorProvider, adds it to the container, and initializes the ErrorProvider.
Private components As System.ComponentModel.IContainerFriend WithEvents errMessage As System.Windows.Forms.ErrorProviderPrivate Sub InitializeComponent() Me.components = New System.ComponentModel.Container Me.errMessage = New _ System.Windows.Forms.ErrorProvider(Me.components) CType(Me.errMessage, _ System.ComponentModel.ISupportInitialize).BeginInit() CType(Me.errMessage, _ System.ComponentModel.ISupportInitialize).EndInit()End Sub
Unfortunately the InitializeComponent subroutine isn’t actually called anywhere, so you need to give the provider a constructor that does call it. The following code shows the IntegerRangeProvider’s constructor:
Public Sub New() InitializeComponent()End Sub
Now when the provider needs to display an error message, it simply calls the ErrorProvider’s SetError method to give a TextBox an error message.
The code below shows how the IntegerRangeProvider class validates the contents of a TextBox. If the TextBox contains no text, the provider sets its msg variable to an empty string. Otherwise, if the TextBox contains a non-numeric value, the code sets msg to the message “Value is not numeric.”
If the TextBox contains a non-blank, numeric value, the provider converts it into an integer and compares it to the TextBox’s MinValue and MaxValue properties. If the value is out of range, the code sets msg to an appropriate message. If the value checks out okay, the code sets msg to a blank string.
Finally, the code uses the ErrorProvider’s SetError method to set the TextBox’s error message appropriately.
' See if the value is between the min and max value.Private Sub Extendee_Validating(ByVal sender As Object, _ ByVal e As System.ComponentModel.CancelEventArgs) Dim extendee As TextBox = DirectCast(sender, TextBox) Dim msg As String = "" If extendee.Text.Trim.Length = 0 Then msg = "" ElseIf Not IsNumeric(extendee.Text) Then msg = "Value is not numeric" Else Try Dim value As Integer = Integer.Parse(extendee.Text) Dim min_value As Integer = GetMinValue(extendee) If value < min_value Then msg = "Value is less than " & min_value Else Dim max_value As Integer = GetMaxValue(extendee) If value > max_value Then msg = "Value is greater than " & max_value End If End If Catch ex As Exception msg = ex.Message End Try End If errMessage.SetError(extendee, msg)End Sub
Often a control does almost?but not quite exactly?what you need. You can make a new control derived from the almost perfect one and modify it, but that’s a lot of work and can give you some very complicated code to maintain.ExtenderProviders give you a useful alternative. They let you add new properties to existing components, let you catch a component’s events, and take appropriate actions. By deriving an ExtenderProvider from an existing control (such as Label) or by including an existing component (such as ErrorProvider) inside the provider, you can essentially add new behaviors to existing classes. Now if only the same worked in day-to-day life. I wouldn’t mind adding new behaviors to traffic lights, parking meters, politicians, the Internal Revenue Service, and a few others.