|
|
|
Select your preferred language |
The design time environment of Visual Studio is a far cry from even
relatively recent versions of the IDE prior to the advent of .NET. C++ users had
a very much more basic experience than that of Visual Basic users but now, the
drag and drop Rapid Application Development environment is here to stay.
One of the greatest features of Visual Studio.NET is the ease with which a
programmer can create a control and use it in the design time, also in a very
RAD fashion.
There are many controls and components in the .NET framework namespaces that
can be used to create great applications very quickly but it's true to say that
some of them could be improved a wee bit. Interestingly enough. Visual Studio
provides us with the opportunity to make those improvements without even
changing the control we want to modify.
This can be accomplished by creating an Extender Provider that uses features
of the designer to seemingly add properties and abilities to component or
control.
An extender provider does the following.
- Adds one or more properties to a specific type of component or a range of
components.
- Uses the events, methods or properties of the extended component to
perform some action at run-time
Interfaces and attributes
An extender provider relies on one interface and an attribute for it's
abilities. The interface is IExtenderProvider and the attribute is ProvidePropertyAttribute.
At design time, whenever a component or control is selected and placed in the
property editor, the IDE will query all the components on a form to see if they
implement IExtenderProvider. If they do the control is given the
opportunity to say whether it provides extra properties for the control. This is
done through the CanExtend method. If the component has properties to add
to a specific control the CanExtend method should return true.
When a component can extend another, the actual property or properties to be
added, there can be more than one, are declared using the ProvidePropertyAttribute.
For each property provided there must be some accessor methods and these are
written into the ExtenderProvider as ordinary methods with a special name. For
example, if you provide the property "Fred" you will make methods
called "GetFred" and "SetFred" in the extender provider.
The Getxxx and Setxxx methods are passed the reference to the component being
extended. It's up to your code to store or retrieve the property data associated
with that particular control reference. The normal way of doing this is to
associate the reference with the data in a hash table. Any other method of
storage you like is also allowed.
The bare bones
Here is the bare bones of a simple extender provider. It enables you to add a
string of text to any MenuItem object that can be used to display menu help on a
status bar or status bar panel.
using
System;
using
System.Drawing;
using
System.Collections;
using
System.ComponentModel;
using
System.Windows.Forms;
namespace
WellFormed
{
///
<summary>
///
MenuStatusExtender adds a text message to MenuItem objects that can be displayed
in the application's status bar.
///
</summary>
[
ToolboxItem(true),
ToolboxBitmap(typeof(MenuStatusExtender),"MenuStatusExtender.bmp"),
ProvideProperty("StatusText",typeof(MenuItem))
]
public class
MenuStatusExtender : Component, IExtenderProvider
{
private
Hashtable _extendees=new Hashtable();
public
MenuStatusExtender()
{
}
#region
IExtenderProvider Members
public
bool CanExtend(object
extendee)
{
if(extendee
is MenuItem)
return
true;
return
false;
}
#endregion
public
string GetStatusText(MenuItem item)
{
if(_extendees.ContainsKey(item))
return
(string)_extendees[item];
else
return
"";
}
public
void SetStatusText(MenuItem item, string
statustext)
{
if(_extendees.ContainsKey(item))
_extendees[item]=statustext;
else
_extendees.Add(item,statustext);
}
}
}
Imports
System
Imports
System.ComponentModel
Imports
System.Collections
Imports
System.Drawing
Imports
System.Windows.Forms
<ToolboxItem(True),
_
ToolboxBitmap(GetType(MenuStatusExtender),
"MenuStatusExtender.bmp"), _
ProvideProperty("StatusText",
"System.Windows.Forms.MenuItem") _
>
_
Public
Class MenuStatusExtender
Inherits
Component
Implements
IExtenderProvider
Private _extendees
As New Hashtable
Public Sub
New()
End Sub
Protected Overloads
Overrides Sub
Dispose(ByVal disposing As
Boolean)
_extendees.Clear()
MyBase.Dispose(disposing)
End Sub
Public Overridable
Function CanExtend(ByVal
extendee As [Object]) As
Boolean Implements
IExtenderProvider.CanExtend
Return
TypeOf extendee Is
MenuItem
End Function
Public Function
GetStatusText(ByVal item As
MenuItem) As String
If
_extendees.ContainsKey(item) Then
Return
CType(_extendees(item), String)
Else
Return
""
End
If
End Function
Public Sub
SetStatusText(ByVal item As
MenuItem, ByVal s As
String)
If
_extendees.ContainsKey(item) Then
_extendees(item)
= s
Else
If
Not Me.DesignMode
Then
AddHandler item.Select, AddressOf
Me.MenuStatusExtender_Select
End
If
_extendees.Add(item,
s)
End
If
End Sub
End
Class
Doing something useful
When an ExtenderProvider has insinuated it's properties into another object,
something useful has to be done with them. At runtime however, nothing in the
framework cares about the extender provider like the IDE does so it's left to
fend for itself as far as finding out exactly how to deal with this new
information it is holding on behalf of it's extendees.
This is when events can be used to bring the extender provider to life at the
right moment.
The code in the demo shows a simple extender provider that can store a string
and associate it with a menu item. The goal is to have that string turn up on
the status bar of the form whenever the user floats their mouse over the menu.
As the menu operations take place, the menu's generate a stream of events that
can be handled and something useful done with them. In the case of this
particular job, our interest lies in the MenuItem.Select event.
The extender provider will make an event available which can provide a string
containing our message. To create this event, a delegate is created and a new
event argument supplied that can be used to pass the message on to our code.
The delegate and the event argument definition is listed below.
public delegate
void
ItemSelectEventHandler(object
sender, ItemSelectEventArgs e);
public class
ItemSelectEventArgs : EventArgs
{
protected
ItemSelectEventArgs()
{
}
public
ItemSelectEventArgs(string
message)
{
_statusMessage=message;
}
string _statusMessage;
public string
StatusMessage
{
get{return
_statusMessage;}
}
}
Public
Class ItemSelectEventArgs
Inherits
EventArgs
Private _statusMessage
As String
Protected Sub
New()
End Sub
Public Sub
New(ByVal
message As String)
_statusMessage = message
End Sub
Public ReadOnly
Property StatusMessage() As
String
Get
Return
_statusMessage
End
Get
End Property
End
Class
Public
Delegate Sub
ItemSelectEventHandler(ByVal sender As
Object, ByVal e As
ItemSelectEventArgs)
Once the delegate and event arguments are in place it's possible to have the
extender provider wire itself to the events of the extendee. The modification to
the SetStatusText method and the other required code to provide an event in the
extender is shown below.
public
void
SetStatusText(MenuItem item, string
statustext)
{
if(_extendees.ContainsKey(item))
_extendees[item]=statustext;
else
{
_extendees.Add(item,statustext);
item.Select+=new
EventHandler(MenuStatusExtender_Select);
}
}
private
void
MenuStatusExtender_Select(object
sender, EventArgs e)
{
OnItemSelected(new
ItemSelectEventArgs((string)_extendees[sender]));
}
protected
void
OnItemSelected(ItemSelectEventArgs e)
{
if(ItemSelected!=null)
ItemSelected(this,e);
}
public
event
ItemSelectEventHandler ItemSelected;
Public Sub
SetStatusText(ByVal item As
MenuItem, ByVal s As
String)
If
_extendees.ContainsKey(item) Then
_extendees(item)
= s
Else
If
Not Me.DesignMode
Then
AddHandler item.Select, AddressOf
Me.MenuStatusExtender_Select
End
If
_extendees.Add(item,
s)
End
If
End Sub
Private Sub MenuStatusExtender_Select(ByVal
sender As Object,
ByVal e As
EventArgs)
OnItemSelected(New
ItemSelectEventArgs(_extendees(sender)))
End Sub
Private Sub OnItemSelected(ByVal e As
ItemSelectEventArgs)
RaiseEvent
ItemSelected(Me, e)
End Sub
Public Event
ItemSelected As ItemSelectEventHandler
Good housekeeping.
The MenuStatusExtender is almost ready. All that needs to be done now is
ensure that when the control is destroyed, all references to Select events in
the various menu items are removed. To do this, a destructor and dispose
override are added to the component as shown below.
~MenuStatusExtender()
{
Dispose(true);
}
protected
override void
Dispose(bool
disposing)
{
foreach(object
o in _extendees.Keys)
((MenuItem)_extendees[o]).Select-=new
EventHandler(this.MenuStatusExtender_Select);
_extendees.Clear();
base.Dispose
(disposing);
}
Protected Overloads
Overrides Sub
Dispose(ByVal disposing As
Boolean)
Dim
mi As MenuItem
If
Not Me.DesignMode
Then
For
Each mi In _extendees.Keys
RemoveHandler mi.Select, AddressOf
Me.MenuStatusExtender_Select
Next
End
If
_extendees.Clear()
MyBase.Dispose(disposing)
End Sub
Testing the MenuStatusExtender.
Once compiled, the component may be placed in the toolbox by dragging it or
by right clicking the toolbox, selecting "Add/remove items" and
browsing to the MenuStatusExtender DLL.
By virtue of the attributes on the class and the bitmap placed in the
resources, the icon for the tool will be seen in the toolbox Figure
1 shows this process.

Figure 1. Placing the control in the toolbox.
The MenuStatusExtender can now be dragged to a form. When a
MainMenu and some MenuItems have been added, you'll see the new property "StatusText
on menuStatusExtender1" in the property grid. Status strings may now be
added to the MenuItems.
Selecting the menuStatusExtender1 component in the tool tray
below the form will enable you to add an event handler to the extender which
will inform your code when a status message us available. This message can then
be placed in a status bar or indeed, any other item that displays text. Figure 2
shows this in action on a simple form.

Figure 2: Status is displayed
Figure 3 shows the form designer at work.

Figure 3: Editing the extended properties.
Finally, the event handler for the
MenuStatusExtender.ItemSelected event is shown below.
private
void
menuStatusExtender1_ItemSelected_1(object
sender, WellFormed.ItemSelectEventArgs e)
{
this.statusBar1.Text=e.StatusMessage;
}
Private
Sub
MenuStatusExtender1_ItemSelected(
ByVal
sender
As
Object
,
ByVal e As
WellFormed.ItemSelectEventArgs) Handles
MenuStatusExtender1.ItemSelected
Me.StatusBar1.Text =
e.StatusMessage
End
Sub
Summary
This article has shown you how to create an extender provider
component, how to use the Getproperty and Setproperty methods to
store and retrieve data which is associated with another item on the form and
how to use the events of the extended item to retrieve the data at runtime.
All sources for the MenuStatusExtender
project can be downloaded from below locations.
Return to the main index.