Introduction
Visual Studio offers an elegant
method to add extra properties to all or some types of controls,
in a way that resembles multiple inheritance. Traditionally you would have to
subclass all controls that need an extra property, put them in a
class library and add the subclassed controls to the Toolbox for
design-time support.
The alternative is to create a component that implements the IExtenderProvider
interface. Drop it in the component tray and Visual Studio shows its properties
with all suitable controls on the form. The WinForms ToolTip provider is an
example of a provider component.
This article assumes you have a basic understanding of the way provider
components work. If you haven't encountered the IExtenderProvider before, read
one of the introductory articles first:
The IExtenderProvider mechanism is usually described for use in WinForms, and
there's a good reason for that.
At the bottom of the
MSDN library article it says:
NoteThe implementation of an
extender provider for Windows Forms controls is different from that
for
ASP.NET server controls.
That's right. It doesn't work for ASP.NET server controls, because the
required design-time support in Visual Studio 2003 is broken. The good news is
that there's a workaround.
This article presents a class that can be
used to develop extender providers for ASP.NET server controls. First we
show what's going wrong in Visual Studio and how we fix it. If you're only
interested in the component, skip that part and read only the section
Using the
component.
We only discuss extender providers implemented as components. The
DefaultButtons
control by Andy Smith is an example of an extender provider based on a web
control.
What's wrong with Visual Studio?
Let's say that we want to write an extender provider, MyProvider,
that does something terribly useful. It adds a single
property to all web controls, a boolean called DoMagic:
[ProvideProperty("DoMagic", typeof(System.Web.UI.Control))]
public class MyProvider : System.ComponentModel.Component, IExtenderProvider
{
// IExtenderProvider implementation
public bool CanExtend (object AExtendee)
{
return (AExtendee is System.Web.UI.Control)
}
// Retrieve the value of the DoMagic property for a control
public bool GetDoMagic (object AExtendee)
{
if (_Values.ContainsKey (AExtendee))
{
return _Values[AExtendee];
}
else
{
return false;
}
}
// Set the value of the DoMagic property for a control
public void SetDoMagic (object AExtendee, bool AYes)
{
_Values[AExtendee] = AYes;
if (AYes)
{
(AControl as System.Web.UI.Control).PreRender += new EventHandler (MagicStuff);
}
}
private void MagicStuff (object sender, EventArgs e)
{
if (GetDoMagic (sender))
{
// Here the magic happens
}
}
// The provider component stores the property values
private Hashtable _Values = new Hashtable ();
}
Put this component in a class library, compile the library, add the
component to the Toolbox and drop it in the component tray of a new ASP.NET
page. All web controls in the page now have an extra property, DoMagic.
Visual Studio automatically adds an InitializeComponent method to
the
code behind the page:
private void InitializeComponent()
{
this.MyProvider1 = new MyProvider();
}
Add a label and a textbox to the page and set the value of DoMagic
to
true for both of them. You would expect that the InitializeComponent
method now reads:
private void InitializeComponent()
{
this.MyProvider1 = new MyProvider();
this.MyProvider1.SetDoMagic (this.Label1, true);
this.MyProvider1.SetDoMagic (this.TextBox1, true);
}
But instead it says:
private void InitializeComponent()
{
this.MyProvider1 = new MyProvider();
this.MyProvider1.SetDoMagic (this, false);
}
Visual Studio does not know how to store the values for provided
properties! It should add a SetDoMagic call for each control that
has
a non-default property value, but instead it adds a single call to SetDoMagic
for the page object, always passing the default property value. A little
more research shows that it adds a SetXXX call for each property
XXX
that has been declared by a ProvideProperty attribute for a type
that
matches the page object, provided that the extender can extend the page
object.