Adding Regular Expression Validation
Duncan Mackenzie
Microsoft Developer Network
May 2002
Summary: Covers the basics of inheriting from another control and adding your own properties, methods, and events along with adding your own code; one of a series of Microsoft Windows Forms control development samples to be read in conjunction with associated overview article. (12 printed pages)
Download WinFormControls.exe.
This article is the second in a five-article series on developing controls in Microsoft® .NET:
- Developing Custom Windows Controls Using Visual Basic .NET (overview)
- Adding Regular Expression Validation
- Combining Multiple Controls into One
- Extending the TreeView Control
- Drawing Your Own Controls Using GDI+
Contents
Introduction
Sample 1a: Adding Regular Expression Validation to the TextBox Control
A Short Discussion on Regular Expressions
What Do I Need to Build?
Sample 1b: Adding Regular Expression Validation an Alternative Way
Summary
Introduction
Two samples are covered in this article: creating a custom TextBox that performs regular expression validation, and creating an extender provider that uses a different method to perform the same regular expression validation.
Sample 1a: Adding Regular Expression Validation to the TextBox Control
In the overview article, I showed you how to quickly enhance the TextBox control to accept only numeric data, creating a new control with a minimum amount of work on your part. That example was only intended to illustrate how quickly a control can be created through the use of inheritance, so it was quite limited. For my first sample, I will show you how to build a control that accomplishes this same goal in a more elegant fashion. Instead of simply expanding this control to support currency and other numerical formats, a better approach is to create a more generic control to restrict text entry. When you are building ASP.NET (Web) applications, a control has already been provided to accomplish exactly what I am describing: the RegularExpressionValidator Control documented in the .NET Framework General Reference. This control allows you to restrict text entry to any format that can be described with a regular expression, which essentially means any format at all. Since a version of this control hasn't been provided for use in Windows Forms, it makes a great example of a custom control that you can build—a Windows Form TextBox that validates its contents against a regular expression.
A Short Discussion on Regular Expressions
Before I jump into the control development, you might be interested in learning about Regular Expressions. Here are a few resources to help you out:
- .NET Framework Regular Expressions
This section of the .NET Framework documentation provides both an introduction to the concept and purpose of Regular Expressions and a reference to using the relevant classes within the Framework. - Regular Expression Samples on the Regular Expression Library Web site
After you've read the material in the .NET Framework documentation, this Web site provides a large list of sample regular expressions that you can use.
What Do I Need to Build?
To create my validating TextBox, I will have to work through a few different steps. First, a new empty control that inherits from TextBox needs to be created. Next, a new property will be added to hold the regular expression to validate against, and a private function will be written that checks the contents of the TextBox against the regular expression property. Finally, by overriding the OnValidating event of the control, I can check if the control's contents are valid and cancel the event.
Step 1: Create a new custom control
If you are developing controls for use by yourself or others, it is a good idea to build them into their own project so that they can be shared as a compiled assembly (a DLL in this case), instead of passing the code around. With that in mind, I will create a new Windows Control Library project to hold the new control, and then create a regular Windows Application project in the same solution to make it easy to test the control. By default, if you create a new Windows Control Library project, a new blank User Control will be added as a starting point. In this case I don't want to use a User Control, since I am inheriting from an existing control, so I am going to delete that file and just add a new class file.
Note You could also simply open up the code view for the User Control item, delete all the code, and then add your own, instead of deleting the item and adding a new class file.
Next, to specify that I want this new class to inherit from the TextBox class, I add an Inherits statement to the blank class Microsoft® Visual Studio® .NET creates:
Public Class RegExTextBox
Inherits System.Windows.Forms.TextBox
End Class
With just that little bit of code I have already created my own version of the TextBox by inheriting from the existing control. Since I haven't added any of my own code, this new control would look and behave exactly like a regular TextBox; inheriting does not change anything until you start adding code or attributes.
Step 2: Add a new property
The next step in building this control is to add a new String property to hold the regular expression statement. Just to be creative, I'll call it RegularExpression, and in addition to the property itself, I will also be creating an internal variable to hold the property's value, m_RegularExpression. Although this class describes a Windows Form control, adding a new property is no different than adding it to any other Microsoft Visual Basic® .NET class:
Private m_RegularExpression As String
Public Property RegularExpression() As String
Get
Return m_RegularExpression
End Get
Set(ByVal Value As String)
m_RegularExpression = Value
End Set
End Property
To use this new property and check the validity of the TextBox contents, use the System.Text.RegularExpressions namespace. Following the examples for that reference documentation, I can create a new RegEx object specifying the RegularExpression property in the constructor and then use that object to check if the TextBox contents match the expression:
'added a Imports System.Text.RegularExpressions to the
'top of the class to simplify the code
Private Function IsValid(ByVal Contents As String) As Boolean
Dim myRegEx As New Regex(RegularExpression)
Dim myMatch As Match
myMatch = myRegEx.Match(Contents)
Return myMatch.Success
End Function
If I could try out the control at this point I would realize that this code isn't correct, because it will return True if a match is found anywhere inside the string. With this code, a regular expression designed to find five-digit zip codes would match the first five digits in a string such as "23434fred" despite the extra text added on at the end. What we actually want to test is whether the string and the expression are exactly matched. Adding a check to make sure that the match found is equal to the entire string will make this routine work correctly. At the same time, I also wrapped this test in a very simple error-handling block so that an invalid RegularExpression causes a return value of False:
'added a Imports System.Text.RegularExpressions to the
'top of the class to simplify the code
Private Function IsValid(ByVal Contents As String) As Boolean
Try
Dim myRegEx As New Regex(RegularExpression)
Dim myMatch As Match
myMatch = myRegEx.Match(Contents)
If myMatch.Success _
AndAlso myMatch.Index = 0 _
AndAlso myMatch.Length = Contents.Length Then
Return True
Else
Return False
End If
Catch
'some form of error in parsing the pattern
Return False
End Try
End Function
This code will only return True if the match is found and covers the entire length of the Contents string, which is exactly what I want.
Step 3: Override OnValidating
Windows Forms controls provide a variety of methods and events dealing with validation already, so it seems appropriate to fit my new RegularExpression check into that existing model by checking the TextBox contents in the OnValidating method. This method will be called whenever the control loses focus and the control's CausesValidation property is equal to True. To make this control useful even when Validation is not being used (CausesValidation = False) a read-only property (Valid) is also added to support directly checking for valid content:
Public ReadOnly Property Valid() As Boolean
Get
Return IsValid(Text)
End Get
End Property
Protected Overrides Sub OnValidating(ByVal e As _
System.ComponentModel.CancelEventArgs)
If Not Valid() Then
e.Cancel = True
End If
MyBase.OnValidating(e)
End Sub
Put all of this code together and you have a complete control that you can try out. Build the control project, and then go into the test project (a Windows Application that has been added to the same solution), add a reference to your RegExTextBox project and customize the toolbox to show your new control. Once you've added the new TextBox to your Windows Form, you can use the standard property window to edit the RegularExpression property of the control. Some examples of regular expressions you might want to try are listed here:
- Simple Zip Code, checks for 5 numeric digits: "\d{5}".
- Date Format, checks for MM/DD/YYYY: "\d{1,2}\/\d{1,2}/\d{4}".
Note that it doesn't check the values, only the format, so 34/58/2341 would be perfectly correct even though it isn't a proper date value.
Putting properties into categories and adding a toolbox bitmap
Using the control in another project reveals a few cosmetic details that need to be changed to make it look professional. The first issue is that our properties are appearing at the bottom of the Properties list under the Misc category, which is the default behavior if you do not set a specific category.
Assigning properties to categories
By adding an attribute to the property definition, it can easily be placed into a category or even hidden from the Properties window altogether. For this control, there are two properties: Valid and RegularExpression. I am going to put RegularExpression in the Behavior category, but there is no real restriction on where you put a specific property; you can even create your own category. The System.ComponentModel.Category attribute is what you will use to specify the appropriate category, but note that it just takes a string as an argument, so make sure you spell the category name correctly:
<System.ComponentModel.Category("Behavior")> _
Public Property RegularExpression() As String
Get
…
As a Canadian, I originally put in the "correct" spelling for behavior and ended up with a completely new category in my Properties window.
Figure 1. You can use any category name you wish and it will appear as a section of the property window.
Correct category name or not, you may notice that my new property is missing a description (which would normally show up in the bottom pane of the Properties window) and, as you may have guessed, another attribute can be used to fix this. The System.ComponentModel.Description attribute accepts a string as an argument that is then displayed whenever that property is selected in the Properties window:
Private Const regularExpressionDescription As String _
= "The Regular Expression used to validate the contents of this TextBox"
<System.ComponentModel.Category("Behavior"), _
System.ComponentModel.Description(regularExpressionDescription)> _
Public Property RegularExpression() As String
Get
Valid, unlike RegularExpression, is not very useful at design time (it is read-only and calculated based on the contents of the TextBox), so I do not want it to appear in the Properties window in any category. Happily, another attribute can take care of this for me: the System.ComponentModel.Browsable attribute, which accepts a Boolean argument indicating whether the property should be visible in the Properties window:
<System.ComponentModel.Browsable(False)> _
Public ReadOnly Property Valid() As Boolean
Get
Return IsValid(Text)
End Get
End Property
Adding a toolbox bitmap
Another problem with the appearance of my new control is that I have not specified a toolbox icon, so the default control icon is being used instead. Just like the property categories, adding a toolbox icon is handled through the use of attributes, the ToolboxBitmap attribute in this case. This attribute allows you to specify a path to an actual (16-x-16) bitmap file, or to specify a toolbox bitmap that is embedded into an assembly as a resource. In this example, I will just specify a file path and use a toolbox bitmap that I created in Paint:
<ToolboxBitmap("C:\mytoolboxbitmap.bmp")> _
Public Class regexTextBox
Inherits System.Windows.Forms.TextBox
Sample 1b: Adding Regular Expression Validation an Alternative Way
Building your own version of the TextBox control is one way to add some additional functionality, but in .NET you could also achieve a similar result by using an extender provider object. Extender providers, such as the ErrorProvider or ToolTip components that already exist in Windows Forms, allow you to dynamically add properties to other controls. Using such a component, I could choose to add a RegularExpression property to all of the TextBox controls on my form, and provide validation services to any control that had a value for that new property. In this specific case, the end result is not that different from building your own TextBox variant, but the extender provider approach works well for any type of functionality that you wish to apply across a group of controls at once.
Creating Your Own Extender Provider
Unlike other controls, extender providers are created by implementing an interface, not through inheritance. This interface (IExtenderProvider) defines only the single method, CanExtend, but that single method works in combination with a variety of class-level attributes to describe the functionality of your provider. Since you will want to manipulate your extender provider in the Windows Forms environment, you will want it to inherit from either Component or Control, in addition to implementing the IExtenderProvider interface:
Public Class regexRevisted
Inherits System.ComponentModel.Component
Implements System.ComponentModel.IExtenderProvider
End Class
Adding attributes for each property
Once you have your class declared, you need to add an attribute for each property you want to provide to other controls. In this case, I want to add a ValidationExpression property that holds the regular expressions for each TextBox control, and a Valid property that returns a True or False value to indicate whether the current ValidationExpression is being met. The attribute I need to add is System.ComponentModel.ProvideProperty, and it takes two parameters for its constructor: the name of the new property you wish to provide, and the type of control that this new property will be added to. So, in my case I added the following code:
<ProvideProperty("ValidationExpression", GetType(TextBox)), _
ProvideProperty("Valid", GetType(TextBox))> _
Public Class regexRevisted
Note The documentation for ProvideProperty had me quite confused; I was convinced that the second parameter was supposed to be the type of the property and I tried and tried to get it to work using GetType(String). Luckily, someone spotted my error pretty fast when I passed it around here at Microsoft, and I changed it to GetType(TextBox).
Creating property routines
For each property you define using your attributes, you must also provide Get and Set routines to allow the property to be viewed and edited on all the other controls. These routines are associated with the properties you provide only by name:
Public Function GetValidationExpression(ByVal o As TextBox) As String
Public Sub SetValidationExpression(ByVal o As TextBox, _
ByVal ValidationExpression As String)
In these property routines you will need to persist the values set for each control so that you can keep track of which property value applied to which control. To accomplish this in my code, I added a NameValueCollection to keep track of the ValidationExpression property for each control that chooses to set a value. A NameValueCollection allows you to associate a collection of string values with string key values, making it very similar to a Dictionary object but strongly typed to work only with strings. In this case, I associated a single ValidationExpression entry with the corresponding control's name. Control names are required to be unique on the same form, so I can be sure that I won't have any conflicts:
Dim RegExpressions As New _
System.Collections.Specialized.NameValueCollection()
Public Function GetValidationExpression(ByVal o As TextBox) As String
Return RegExpressions.Get(o.Name)
End Function
Public Sub SetValidationExpression(ByVal o As TextBox, _
ByVal ValidationExpression As String)
RegExpressions.Set(o.Name, ValidationExpression)
End Sub
In addition to setting and retrieving the regular expression value, I also add an event handler to any control that has set a non-empty value (indicating that the control wants validation). This event handler is set up to trap the OnValidation event of each TextBox, allowing my ExtenderProvider to validate the contents of the TextBox just like the RegExTextBox does (see Sample 1a). If the validation fails, an event of the ExtenderProvider is raised, allowing for a single event handler to trap all the validation errors of the associated controls:
Public Sub SetValidationExpression(ByVal o As TextBox, _
ByVal ValidationExpression As String)
RegExpressions.Set(o.Name, ValidationExpression)
If ValidationExpression <> "" Then
AddHandler o.Validating, _
New System.ComponentModel.CancelEventHandler(AddressOf OnValidating)
Else
RemoveHandler o.Validating, _
New System.ComponentModel.CancelEventHandler(AddressOf OnValidating)
End If
End Sub
Private Sub OnValidating(ByVal sender As Object, _
ByVal e As System.ComponentModel.CancelEventArgs)
Dim tb As TextBox
If TypeOf sender Is TextBox Then
tb = CType(sender, TextBox)
Dim validationExpression As String
validationExpression = RegExpressions.Get(tb.Name)
If validationExpression <> "" Then
If Not IsValid(validationExpression, tb.Text) Then
RaiseEvent ValidationError(tb, _
New System.EventArgs())
End If
End If
End If
End Sub
Notice that I remove the handler if the ValidationExpression is set to an empty string; why handle events or otherwise work with a control that doesn't want anything to do with my provider? Now, the Valid property is a bit easier to implement, as it is a read-only property and needs only a GetValid function to be created. This function (actually calling another function, IsValid) checks the contents of a specific TextBox against its ValidationExpression and returns True or False depending on whether the contents matched the expression:
Private Function IsValid(ByVal validationExpression As String, _
ByVal input As String) As Boolean
Dim reCheck As New Regex(validationExpression)
Dim myMatch As Match
myMatch = reCheck.Match(input)
If Not (myMatch.Success AndAlso _
myMatch.Index = 0 AndAlso _
myMatch.Length = input.Length) Then
Return False
Else
Return True
End If
End Function
Public Function GetValid(ByVal o As TextBox) As Boolean
Dim validationExpression As String
validationExpression = RegExpressions.Get(o.Name)
If validationExpression <> "" Then
Return IsValid(validationExpression, o.Text)
Else
Return True
End If
End Function
Implementing the CanExtend function
Although it seems logical to create this function first, since the IExtenderProvider interface requires it to exist and will be complaining until you add it, adding my implementation of CanExtend was the last piece of code I wrote. The key task of CanExtend is to return True or False for every control it is passed, indicating whether my two new properties should be applied to that specific control. In the case of my ExtenderProvider, I want these properties applied to each and every TextBox, so I simply check the type of the control supplied and return True if it is a TextBox or false if it is not. Once I have decided to handle a control, I also add a blank entry into my NameValueCollection as a placeholder for a possible ValidationExpression:
Public Function CanExtend(ByVal extendee As Object) As Boolean _
Implements System.ComponentModel.IExtenderProvider.CanExtend
If TypeOf extendee Is System.Windows.Forms.TextBox Then
RegExpressions.Set(CType(extendee, TextBox).Name, "")
Return True
Else
Return False
End If
End Function
Finishing touches
As with the earlier control, there are always a few additional attributes that can be set or additional code that could be added to make your control more useful or more professional in appearance. In the case of my ExtenderProvider, I do not want programmers to be accidentally calling the Get and Set routines for my two new properties, so I can hide them from Microsoft Intellisense® using the EditorBrowsableAttribute:
<EditorBrowsableAttribute(EditorBrowsableState.Never)> _
Public Function GetValid(ByVal o As TextBox) As Boolean
If you want more information on ExtenderProvider, you can view a walkthrough of this process in the .NET Framework documentation, and the code for this sample is supplied as part of the download for this article. The finished ExtenderProvider, called regexRevisited, when placed onto a Windows Form adds two properties, ValidationExpression and Valid, to every TextBox The end result and programmer experience is very similar to replacing every TextBox with the custom regular expression TextBox I created earlier, but all of the code is contained within the single component.
Summary
Both versions of this sample provide the same result—validation of a TextBox using regular expressions—but they do it in different ways. It is difficult to recommend one method over the other, as they both work and the code is not all that different, but you should be aware of both possible ways of adding features to Windows Forms controls so that you can choose the most appropriate solution for your particular project.