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.

Summary

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 pkimmel@softconcepts.com.

# # #

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read