Have you ever needed an extra property on an existing control? I am sure that this situation occurred many times. To solve your problem, you probably sub-classed existing controls. But what if you need to add this same property to all the controls you are using even those you are not using yet? Will you subclass them all?
I have a more suited solution for you that are specifically tailored for that purpose. We are not talking of changing the behaviour of controls here. We simply want to add a property to existing control. You want an example of an existing extender provider? The Tooltip and the Error (see my column on this topic in the February 2003 edition) providers are probably the mostly known extenders.
I am sure you already used them but have you ever asked yourself how you can build your own? Once an extender provider component (notice that it is not a control!) is hosted by a form, all the controls of this form are extended.
Try an existing provider
To be able to really understand what is an extender provider, lets first place one on a form and see how it interacts with the controls. So for this, you need to create a new Windows application project. Drop a couple of different controls (labels, textboxes, buttons, checkboxes, …) on it. Now, add a Tooltip component to this very same form. You will first notice that instead of appearing on the form, the Tooltip provider will appear in the component tray. Now if you look back at the properties of any of the controls you placed on the form, you will notice that a new property has been added. This property is called “ToolTip on ToolTip1”. Whatever the value you type in this property will be displayed at run time in a small yellow box when end users hover the control.
Figure 1: The ToolTip provider in action
Unlike traditional properties, these new properties cannot be accessed by their name. You cannot change the ToolTip property of a button by typing something like this:
Button1.Tooltip = "Something new"
This syntax is not valid and will not compile. Instead, you need to use methods provided by the extender like this:
ToolTip1.SetToolTip(Button1, "Something new")
If you don’t trust me, take a minute to look under the “Windows Form Designer generated code” region. You will see that all the values you typed into the properties dialog are translated to the SetToolTip syntax.
Likewise, to read the value placed in the Tooltip property, you need to use this syntax:
Notice that each provider exposes different properties. All these properties will be available through the properties window will always be available by code using the ProviderInstanceName.SetXXX or the ProviderInstanceName.GetXXX syntax.
Steps to create an extender provider
Now that we know how an extender provider interacts with our forms and controls, we can start working on our own extender provider.
The recipe can seem quite cumbersome at first but it is not. Here are the major steps:
Time to demonstrate it
For the demo, we will simply expose a simple description property and display this description somewhere (the ideal place is a status bar – my September 2004 column talked about this control!). This description will also be triggered in an event so the form can react to it and display the description easily.
Step 1 – Create a class
So you first need to add a class. The one catch to this step is that the class needs to be packaged into a separate assembly from the assembly using it. So you normally create a class library project to hold your extender provider (you can add it to the same solution you add before).
So add a project to your current solution (using the Class library template). You can then simply add a class to this new project. I have named my class as MyExtProv.
Step 2 – Inherits from a base class.
You should normally inherit from System.ComponentModel.Component (but in some occasions you may see the extender inheriting from the System.Windows.Forms.Control class).
To inherits, you need to add a line like this:
Public Class MyExtProv
If you inherit the Control class, your provider will appear on the form instead of appearing into the component tray.
Step 3 – Enumerate new properties
You must now list all the new properties you will provide. To do this, you need to add attributes to the class declaration like this:
<ProvideProperty("Description", GetType(Component))> _
Public Class MyExtProv
This attribute says that we are providing a property named Description and this property will be available to any objects that are of the Component Type.
If you give a different type argument (Control for example), only objects that are of that type will be extended. So Timer, Menu, ToolTip and other components won’t be extended but Labels, Buttons, TextBoxes and other controls will.
You may get an error at this stage saying that the ProvideProperty type is not defined. To fix this error, you need to import the class defining it:
Step 4 – Implement the IExtenderProvider interface
Your class must now implements a specific interface. The first thing you need to do is to add this line just after the “Inherits” line:
Adding this line, should automatically add the only method declaration required to implements this interface:
Public Function CanExtend(ByVal extendee As Object) As Boolean _
This method must return a Boolean value telling if the object received in the extendee argument can be extended or not. You can use this method to limit your new properties to specific type only.
In our example, we will simply return a True value to support any component:
Step 5 – Create a class-level HashTable
The extender provider class needs any kind of storage to keep track of controls of a form and the value you set to the properties. You will mostly want to use a HashTable to do that. This storage needs to be visible to all the members of the class.
At the top of the class, you will find a line like this:
Private ctrlProps As New Hashtable
Step 6 – Define the SetXXX and GetXXX methods
You now need to implement the setter and the getter for all new properties. These 2 methods will use the HashTable you declared at the previous steps. Remember that these methods MUST be named Set and Get followed by the EXACT name of the property you specified at step 3.
Here is the setter:
Public Sub SetDescription(ByVal ctrl As Component, ByVal value As String)
'To be sure we don't have the Nothing value
If value Is Nothing Then value = ""
If value.Length = 0 AndAlso ctrlProps.Contains(ctrl) Then
'remove the control from the hashtable if its value is deleted
RemoveHandler CType(ctrl, Control).Enter, _
ElseIf value.Length > 0 Then
If Not ctrlProps.Contains(ctrl) Then
If TypeOf ctrl Is Control Then
AddHandler CType(ctrl, Control).Enter, _
ElseIf TypeOf ctrl Is MenuItem Then
AddHandler CType(ctrl, MenuItem).Select, _
ctrlProps.Item(ctrl) = value
This method will be called each time you set a value through the properties window or when you directly call the SetDescription method. The control (ctrl) and the new description (value) are received as arguments. This method uses the HashTable (ctrlProps) to store the description. You can also see that we are adding a handler on specific event of the controls. This is needed to be able to raise our own event (more on this later).
The getter method is much simpler. It checks the HashTable to return the value previously set:
Public Function GetDescription(ByVal ctrl As Component) As String
If ctrlProps.Contains(ctrl) Then
This step is specific to our extender because this one is triggering an event.
The event needs to be declared at the top of the class like this:
Public Event MyExtProvEvent(ByVal Description As String)
Finally, the setter used the address of ShowDescription that has not been defined yet. This method needs to raise the event:
Private Sub ShowDescription(ByVal sender As Object, ByVal e As System.EventArgs)
RaiseEvent MyExtProvEvent(GetDescription(CType(sender, Component)))
Extender Provider test drive
You are now ready to see your extender provider in action.
To be able to reference your extender from another assembly, the extender needs to be compiled. So go ahead and build the solution (or at least the class library).
In the project you first created (the one containing the form you place a tooltip on), add a reference to your extender assembly (Project->Add Reference…).
Your extender component will now appear into your toolbox. Double-click on it while your form designer is visible to add an instance to your form. If you now look at the properties of any of the controls that are on the form, you should see one named “Description on MyExtProv1”. You see it? It is exactly like the tooltip provider we test first. Set the description of some controls that can get the focus.
Figure 2: Our own description property!
The last thing we need to do is to catch the event sent by the provider to display the description (notice that I am using a simple StatusBar control here):
Private Sub MyExtProv1_MyExtProvEvent(ByVal Description As String) _
StatusBar1.Text = Description
You are now ready to run the application. You should see the value you set as the description appearing in the StatusBar when you go from control to control.
I agree that implementing requires some strict guidelines but I am sure you will agree that it is a lot easier then sub classing each and every control to give the same behaviour.
Keep this sample handy. I am sure you will find occasions to use in the near future unless you already found one!
I hope you appreciated the topic and see you next month.