Creating Custom a Custom Control and UITypeEditor - Part 2
Last time we talked about a custom TextBox control that incorporates a regular expression and validates against that expression. Small, incremental changes like this are invaluable as an accumulative means of growing a reliable toolbox. Unfortunately it is not always convenient or easy to create a test application to test your controls. In the case of the RegexTextBox, each time we add a new regular expression we are literally defining new behavior. You certainly shouldn't have to write a separate test application to verify each expression.
As a preferable alternative we can define a UITypeEditor. The UITypeEditor can be associated with our control and provide us with a means of modifying the properties of a control and even testing those modifications—as is the case with the regular expression—in the IDE at design time. UITypeEditors are used to provide custom editing in the Properties window when the basic text-input editing features aren't satisfactory.
In this second half I will show you how to create a modal dialog UITypeEditor for the RegexTextBox.
Implementing the Editor
Implementing a UITypeEditor requires that we inherit from a UITypeEditor and override two methods: EditValue and GetEditStyle. (If you want to provide custom painting in an editor then override GetPaintValueSupported and PaintValue.) EditValue is used to change the values of the properties of the control associated with the editor and GetEditStyle is used to indicate the kind of editor that will be displayed. The choices are UITypeEditorEditStyle.DropDown, UITypeEditorEditStyle.Modal, and UITypeEditorEditStyle.None. DropDown is returned when we want to edit the value of a property in-place, such as a list of choices, and Dialog is used when we will be displaying a modal dialog. The modal dialog can be used to support advanced design-time interaction and is the kind of editor we will create.
A UITypeEditor has to interact with the VS .NET IDE. There are specific interfaces that represent hooks into the IDE. We will need to get our hands on these IDE hooks and use those to move data between our UITypeEditor, the IDE, and the control we are editing. Listing 1 shows the code for our editor, followed by a synopsis of the code.
Listing 1: A UITypeEditor that displays a modal dialog used to test regular expressions.
1: Imports System.Windows.Forms 2: Imports System.Text.RegularExpressions 3: Imports System.ComponentModel 4: Imports System.Drawing.Design 5: Imports System.Windows.Forms.Design 6: 7: Public Class RegexTextBoxEditor 8: Inherits UITypeEditor 9: 10: Private service As IWindowsFormsEditorService 11: 12: Public Overloads Overrides Function EditValue( _ 13: ByVal context As ITypeDescriptorContext, _ 14: ByVal provider As IServiceProvider, _ 15: ByVal value As Object) As Object 16: 17: If (Not context Is Nothing And _ 18: Not context.Instance Is Nothing And _ 19: Not provider Is Nothing) Then 20: 21: service = CType( _ 22: provider.GetService(GetType(IWindowsFormsEditorService)), _ 23: IWindowsFormsEditorService) 24: 25: If (Not service Is Nothing) Then 26: 27: Dim Instance As RegexTextBox = _ 28: CType(context.Instance, RegexTextBox) 29: 30: Dim Expression As String = Instance.Expression 31: Dim Text As String = Instance.Text 32: If (FormTestExpression.Execute(Text, _ 33: Expression)) Then 34: 35: Instance.Text = Text 36: Instance.Expression = Expression 37: 38: End If 39: 40: End If 41: End If 42: 43: Return value 44: 45: End Function 46: 47: Public Overloads Overrides Function GetEditStyle( _ 48: ByVal context As ITypeDescriptorContext) _ 49: As UITypeEditorEditStyle 50: 51: If (Not context Is Nothing And _ 52: Not context.Instance Is Nothing) Then 53: Return UITypeEditorEditStyle.Modal 54: End If 55: 56: Return MyBase.GetEditStyle(context) 57: End Function 58: 59: End Class
Listing 1 contains the key elements of a UITypeEditor used to edit text values—as opposed to visual effects—at design time in the VS .NET IDE. These elements include namespaces for regular expressions and custom components (lines 1 through 5), inheritance from System.Drawing.Design.UITypeEditor (lines 4 and 8 combined), and the requisite EditValue (lines 12 through 45) and GetEditStyle (lines 47 through 57). We'll start with GetEditStyle because it is shortest.
GetEditStyle accepts a System.ComponentModel.ITypeDescriptorContext and returns a UITypeEditorEditStyle enumerated value. The ITypeDescriptorContext contains context information about a component, which we'll need to modify the component. If context and context.Instance are initialized then we return the UITypeEditorEditStyle.Modal value. Otherwise, we let the inherited GetEditStyle method handle the return value.
EditValue does the real work. To summarize, EdtiValue gets a reference to the specific control we are editing, displays the custom edit behavior, and updates the relevant properties of that control. However, because we are using generically named interfaces it seems more complicated than that.
The overridden EditValue method receives an ITypeDescriptorContext (our context), an IServiceProvider (a supporting object), and value is the component we are editing. In a nutshell the IServiceProvider returns access to an IWindowsFormsEditorService, which is exactly what it sounds like: an interface to the Windows Forms Editor. Again, we check to make sure the context is valid and include a check to make sure the provider is valid on lines 17 through 19. If we have a valid context and provider then we can proceed.
The single statement on line 21 through 23 requests the service, which is type cast to an IWindowsFormsEditorService. Line 25 checks to make sure we have a valid service object. If, again, the conditional check succeeds we proceed. The context (and value) represent our component. Since we know the kind of control we are using here we can perform a type cast, casting the context.Instance to the RegexTextBox we know it is. If we are concerned about the editor being used for something else then we could perform a type check that precedes the type cast. By line 30 we have the properly typecast component; all we need to do is provide an editor for this component. If the user confirms that modifications are acceptable then we save the changes and finish up.
Implementing the editor is as simple as creating a form. I created a Form and added a shared Execute method to simplify interaction with the form. Execute takes input parameters and if I get a True result from the form then I assign the modified values back to the control. Finally the value-the component-is returned and editing is complete. Figure 1 contains an image of the editing form and listing 2 contains the complete listing for the form.
Figure 1: The modal dialog form for the UITypeEditor.
Listing 2: The source code for the modal editor dialog.
1: Imports System.Text.RegularExpressions 2: Imports System.Windows.Forms 3: 4: 5: Public Class FormTestExpression 6: Inherits System.Windows.Forms.Form 7: 8: [ Windows Form Designer generated code ] 9: 10: Public Shared Function Execute( _ 11: ByRef InputValue As String, _ 12: ByRef ExpressionValue As String) As Boolean 13: 14: Dim Form As FormTestExpression = _ 15: New FormTestExpression() 16: 17: Form.Input = InputValue 18: Form.Expression = ExpressionValue 19: 20: Execute = (Form.ShowDialog() = Form.DialogResult.OK) 21: 22: If (Execute) Then 23: InputValue = Form.Input 24: ExpressionValue = Form.Expression 25: End If 26: 27: Form = Nothing 28: End Function 29: 30: Public Property Expression() As String 31: Get 32: Return TextBox2.Text 33: End Get 34: Set(ByVal Value As String) 35: TextBox2.Text = Value 36: End Set 37: End Property 38: 39: Public Property Input() As String 40: Get 41: Return TextBox1.Text 42: End Get 43: Set(ByVal Value As String) 44: TextBox1.Text = Value 45: End Set 46: End Property 47: 48: Private Sub Button3_Click(ByVal sender As System.Object, _ 49: ByVal e As System.EventArgs) Handles Button3.Click 50: 51: If (Test()) Then 52: MessageBox.Show("Passed!", "Test", _ 53: MessageBoxButtons.OK, MessageBoxIcon.Information) 54: Else 55: MessageBox.Show("Failed!", "Test", _ 56: MessageBoxButtons.OK, MessageBoxIcon.Exclamation) 57: End If 58: 59: End Sub 60: 61: Private Function Test() As Boolean 62: Return Regex.IsMatch(Input, Expression) 63: End Function 64: 65: End Class
The code for the form is basic form code. I used properties to simplify interactions to the underlying controls, and the shared method Execute, on lines 10 through 28, is a good, general strategy for managing interactions to modal dialogs. (In essence, the creation and interaction is confined to a single method, rather than everywhere the form is created.)
Associating the Editor with the Control
What is left to do is to associate the UITypeEditor with our custom component. The complete listing for the custom component is not provider. (Leaving it out is a teaser to get you to read the other article; forgive me.)
To associate a UITypeEditor with a custom control we use the EditorAttribute applied to the property we will be editing. Listing 3 shows a partial listing of the RegexTextBox custom control with the proper placement of the EditorAttribute (line 12).
Listing 3: A partial listing of the RegexTextBox with the EditorAttribute applied.
1: Imports System.Windows.Forms 2: Imports System.Text.RegularExpressions 3: Imports System.ComponentModel 4: Imports System.Drawing.Design 5: Imports System.Windows.Forms.Design 6: 7: Public Class RegexTextBox 8: Inherits TextBox 9: 10: Private FExpression As String 11: 12: <Editor(GetType(RegexTextBoxEditor), GetType(UITypeEditor)), _ 13: Description("A regular expression")> _ 14: Public Property Expression() As String 15: Get 16: Return FExpression 17: End Get 18: Set(ByVal Value As String) 19: FExpression = Value 20: End Set 21: End Property 22: 23: Protected Overrides Sub OnValidating( _ 24: ByVal e As CancelEventArgs) 25: If (FExpression = String.Empty OrElse _
The EditorAttribute takes a couple variations of arguments, both of which indicate the editor and that editor's base class. The editing capability will manifest itself as extended behavior in the Properties window. If the UITypEditorEditStyle is Modal then a button with an ellipses will be displayed in the specific property's edit field in the Properties window (see figure 2).
Figure 2: A property with a modal dialog editor will have a button displayed in the edit bar, as shown.
You have the option of entering a literal value or using the button to display the dialog (shown in figure 1).
Testing the Type Editor
Clearly testing the editor can be accomplished by clicking the elliptical button shown in figure 2. I encourage you to perform this kind of direct testing. Click the button and enter some text in the Input field and Expression field of the editor (see figure 1).
Direct testing is mandatory and will give you a good opportunity to explore regular expressions in this case. I would also like to take a minute to tell you about www.nunit.org. This site contains a free GUI and console implementation of NUnit. Model after JUnit, this testing suite makes it easy to create automated tests and the implementers did a top-notch job with this product. Download a free copy of NUnit and give it a try; it is a great way to automate testing for .NET.
Some of my readers may not know—and may not be happy to hear—that for years Borland's Delphi has provided excellent support for building custom controls. Those who have had time to explore outside of VB6 have known this. As luck would have it, a key architect of Borland's VCL has been instrumental in bringing us .NET, and as never before VB .NET programmers have a tool that makes it easy and a ton of fun to build professional custom controls.
If you are going to create custom controls, think small incremental changes. If you need some specialized editing then implement a custom UITypeEditor.
There a lot of additional capabilities in .NET, especially when it comes to implementing custom components and controls. To learn more about this subject pick up a copy of my upcoming book, The Visual Basic .NET Developer's Book from Addison-Wesley.
About the Author
Paul Kimmel is a freelance writer for Developer.com and CodeGuru.com. Look for his recent book "Advanced C# Programming" from McGraw-Hill/Osborne on Amazon.com. Paul Kimmel is available to help design and build your .NET solutions and can be contacted at email@example.com.
# # #