[ Team LiB ] |
9.3 Extender ProvidersAs 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 trayAs 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 VBOption 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 useExample 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 ] |