DekGenius.com
[ Team LiB ] Previous Section Next Section

9.3 Extender Providers

As we saw in the previous section, it is possible to extend the set of properties that a control presents at design time by writing a custom designer class and implementing one or more of the metadata filtering methods. But what if you want to extend the property set of more than one control? You could write a designer class that is used as the base class for several controls' designers, but what if you would like to be able to extend the property sets of controls you did not write?

The Forms Designer supports a special kind of component called an extender provider, which is able to extend the property set of any control. For example, the built-in ToolTip component is an extender provider—if you add a ToolTip component to a form, every control on the form gets an extra ToolTip property. (Such properties are known as extender properties.) We already saw how to use extender providers in Chapter 3, but we will now see how to implement such a component.

Extender providers are not controls, because they do not participate directly in the user interface. In the Forms Designer, they appear in the component tray rather than on the form itself, as shown in Figure 9-6. So instead of inheriting from the Control class, an extender provider must derive directly from the Component class, which is defined in the System.ComponentModel namespace.

Figure 9-6. An extender provider in the component tray
figs/winf_0906.gif

As the ToolTip component illustrates, extender providers can augment a control's runtime behavior. Recall from Chapter 3 how an extender property is set at runtime: the Forms Designer generates code that calls a method on the extender provider itself. In this call, it passes in a reference to the object on which the property is notionally being set. Example 9-26 shows the ToolTip extender property being set on a control called button1.

Example 9-26. Setting an extender property
// C# code
this.toolTip1.SetToolTip(this.button1, "This is a button!");

' VB code
Me.toolTip1.SetToolTip(Me.button1, "This is a button!")

In general, anything that augments the behavior of a control is often best implemented as an extender provider, if possible. This gives users of the component much greater flexibility than the alternative and more obvious design choice, inheritance. For example, suppose you want a reusable facility that automatically validates the contents of a text box against a regular expression. You might create a RegExTextBox control that derives from the built-in TextBox control. But what if the user of your control also wants the autocomplete functionality we implemented in the AutoTextBox in Chapter 6? The .NET runtime only supports single inheritance, which means this will be an either/or decision. But if we implement regular expression validation as an extender property, we are free to add it to any derivative of TextBox. (Arguably, autocompletion would also have been better implemented through an extender provider.)

To create an extender provider, we simply write a component that implements the IExtenderProvider interface. This only requires us to implement a single method, CanExtend. The Forms Designer will call this at design time, passing in each component on the form in turn to find out whether our provider wants to extend that component's property set. Our validating extender will just check that the component derives from Control—otherwise, it might end up trying to add validation support to non-visual components. The only other requirement for our provider class is that it must indicate with ProvidePropertyAttribute what properties it will add and provide accessor functions for those properties. Examples Example 9-27 and Example 9-28 show our regular expression validation provider.

Example 9-27. An extender provider written in C#
using System;
using System.Collections;
using System.Windows.Forms;
using System.ComponentModel;
using System.Text.RegularExpressions;

[ProvideProperty("RegexValidate", typeof(Control))]
public class RegexValidator : Component, IExtenderProvider
{
    private Hashtable ht = new Hashtable();

    public bool CanExtend(object extendee)
    {
        return extendee is Control;
    }

    public void SetRegexValidate(Control ctl, string expr)
    {
        if (!DesignMode)
        {
  Validator v = new Validator(expr, ctl);
  ctl.Validating += new CancelEventHandler(v.OnValidating);
        }
        ht[ctl] = expr;
    }

    public string GetRegexValidate(Control ctl)
    {
        return (string) ht[ctl];
    }

    private class Validator
    {
        public Validator(string expr, Control ctl)
        {
  reExpr = expr;
  target = ctl;
        }

        private string reExpr;
        private Control target;
        private Regex re;

        public void OnValidating(object sender, CancelEventArgs e)
        {
  if (re == null)
  {
      re = new Regex (reExpr);
  }
  if (!re.IsMatch(target.Text))
  {
      e.Cancel = true;
  }
        }
    }

}
Example 9-28. An extender provider written in VB
Option Strict On

Imports System 
Imports System.Collections 
Imports System.Windows.Forms 
Imports System.ComponentModel 
Imports System.Text.RegularExpressions 

<ProvideProperty("RegexValidate", GetType(Control))> _
Public Class RegexValidator 
       Inherits Component
       Implements IExtenderProvider

    Private ht As New Hashtable() 

    Public Function CanExtend(extendee As Object) As Boolean _
 Implements IExtenderProvider.CanExtend
        Return TypeOf extendee Is Control 
    End Function

    Public Sub SetRegexValidate(ctl As Control, expr As String)
        If Not DesignMode Then
  Dim v As New Validator(expr, ctl) 
  AddHandler ctl.Validating, AddressOf v.OnValidating 
        End If
        ht(ctl) = expr 
    End Sub

    Public Function GetRegexValidate(ctl As Control) As String
        Return CStr(ht(ctl)) 
    End Function

    Private Class Validator

        Private reExpr As String 
        Private target As Control 
        Private re As RegEx 

        Public Sub New(expr As String, ctl As Control)
  reExpr = expr 
  target = ctl 
        End Sub

        Public Sub OnValidating(sender As Object, e As CancelEventArgs)
  If re Is Nothing Then
      re = New Regex(reExpr) 
  End If
  If Not re.IsMatch(target.Text) Then
      e.Cancel = true 
  End If
        End Sub
    End Class
End Class

The accessors must be functions named GetXxx and SetXxx where Xxx is the name of the extender property. You must provide both a get and a set accessor, or Visual Studio .NET will refuse to use your extender provider. This means we are responsible for remembering what the values have been set to for each control. For this, we just use a Hashtable (from the System.Collections namespace).

When the SetRegexValidate method is called, we check to see if we are in design mode or runtime mode. If this is runtime, we build an instance of the private Validator class. This is a nested class that does the work of validating the control, using the Regex class from the System.Text.RegularExpressions namespace. We just attach the Validator class's OnValidating method to the target control's Validating event. When the focus leaves the relevant control, its Validating event will fire as usual, and our extender provider will use the regular expression to validate the control.

Figure 9-7 shows how this extender property looks in Visual Studio .NET's property panel. (The regular expression here is a somewhat naïve test for a valid-looking email address.) Example 9-29 shows the code that is generated for this property.

Figure 9-7. An extender property in use
figs/winf_0907.gif
Example 9-29. Code generated for an extender property
// C#
this.regexValidator1.SetRegexValidate(this.textBox1, ".*@.*");

' VB
Me.regexValidator1.SetRegexValidate(Me.textBox1, ".*@.*")

For this extender provider to be of much use, you would want to add support for error reporting. This would be easy enough to do—you would simply add a second extender property to hold the error text. You could either display a message box to show the message, or you might use the ErrorProvider class to show the error. (To do this, you would provide a normal property on the extender provider component itself to tell it which ErrorProvider to use.)

    [ Team LiB ] Previous Section Next Section