Introduction
This article demonstrates how regular expressions can be used to validate user input in a Windows Presentation Foundation (WPF) application. The technique presented herein provides two ways for a developer to validate the text of a TextBox
via regular expressions: explicitly adding a ValidationRule
-derived object to the ValidationRules
of a Binding
, or simply using attached properties of a static service provider class.
This code was compiled and tested against the June 2006 CTP of .NET Framework v3.0, but should work properly in later builds of the framework, as well.
Background
WPF provides an approach to input validation which is very different from the way that validation was performed in WinForms and ASP.NET. This article assumes that the reader is familiar with the fundamentals of WPF validation. If you are not yet familiar with validation in WPF, I highly recommend Paul Stovell�s excellent article on the subject. This page in the SDK is also very informative.
The general idea behind input validation in WPF involves the abstract ValidationRule
class. Classes which derive from ValidationRule
must override the Validate
method to perform custom validation logic and return a value which indicates whether the input value is valid or not. Instances of the ValidationRule
-derived classes can be added to a Binding
�s ValidationRules
collection. When it comes time for the bound value to be validated, all of the ValidationRule
-derived objects will be queried to see if the input value is valid.
RegexValidationRule
My goal was to create a reusable means of validating user input via regular expressions. I implemented this by creating the RegexValidationRule
class, which derives from ValidationRule
, and executing a Regex
in its Validate
override. The RegexValidationRule
is designed to validate the Text
property of a TextBox
.
The XAML below shows how to use this class:
<TextBox Name="txtProductCode">
<TextBox.Text>
<Binding Path="ProductCode">
<Binding.ValidationRules>
<jas:RegexValidationRule
RegexText="^[A-Z]{3}\.[0-9]{3}$"
ErrorMessage="Invalid product code"
RegexOptions="IgnoreCase"
/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
The Binding
and RegexValidationRule
created above in XAML can be created in the code-behind, as seen here:
Binding binding = new Binding();
binding.Path = new PropertyPath( "ProductCode" );
RegexValidationRule rule = new RegexValidationRule();
rule.RegexText = @"^[A-Z]{3}\.[0-9]{3}$";
rule.ErrorMessage = "Invalid product code.";
rule.RegexOptions = RegexOptions.IgnoreCase;
binding.ValidationRules.Add( rule );
txtProductCode.SetBinding( TextBox.TextProperty, binding );
The RegexValidationRule
class has three public properties that can be used to adjust its behavior:
RegexText
- stores the regular expression used during validation.
ErrorMessage
- stores the text to be used/displayed when validation fails (such as the message in a tooltip).
RegexOptions
- allows you to modify the RegexOptions
property of the Regex
object used to validate the input value.
When writing a regular expression in a XAML file, be sure to keep in mind that XAML requires certain characters to be escaped, such as ampersands (& = &) and apostrophes ( ' = '). Refer to this page in the SDK for more information about these necessary nuisances.
This is all well and good, but it seems like a lot of effort just to add an instance of the RegexValidationRule
to the Text
property�s associated Binding
in XAML. It would be much more convenient if it was not necessary to create multiple nested elements, just to add a rule to the ValidationRules
collection of a Binding
. The next section of this article shows a much easier way to make use of the RegexValidationRule
class.
RegexValidator
The RegexValidator
class provides a streamlined way of using regular expressions to validate the text of a TextBox
. Through the magic of attached properties, it is possible to avoid the multiple nested elements in XAML, as seen in the previous section, just to add a RegexValidationRule
to a Binding
�s ValidationRules
.
The RegexValidator
is a static class whose sole job is to create RegexValidationRule
objects and add them to the appropriate Binding
�s ValidationRules
. It exposes its functionality via attached properties, so you never have to create an instance of RegexValidator
to use the service it provides. In fact, you cannot instantiate the RegexValidator
class because it is a static class.
Below is an example of the RegexValidator
in use:
<TextBox Name="txtProductCode"
Text="{Binding Path=ProductCode}"
jas:RegexValidator.RegexText="^[a-zA-Z]{3}\.[0-9]{3}$"
jas:RegexValidator.ErrorMessage="Invalid product code."
/>
The XAML seen above could be expressed in C# like this:
Binding binding = new Binding();
binding.Path = new PropertyPath( "ProductCode" );
this.txtProductCode.SetBinding( TextBox.TextProperty, binding );
RegexValidator.SetRegexText(
this.txtProductCode, @"^[a-zA-Z]{3}\.[0-9]{3}$" );
RegexValidator.SetErrorMessage(
this.txtProductCode, "Invalid product code." );
As you can see, the RegexValidator
is much easier to use than the RegexValidationRule
, particularly when being used in XAML. You simply specify the values of attached properties, and the rest of the work is taken care of for you.
The astute observer will notice that this is a modified version of the example used in the previous section, but it does not set the RegexOptions
property. I decided to omit the RegexOptions
property from RegexValidator
because I suspect that property will not be used very often (I could be wrong about that!). The benefit of omitting that property from RegexValidator
is that the class�s API is kept very small and simple. If you need to set the RegexOptions
property, you will need to create a RegexValidationRule
explicitly, as seen in the previous section of this article.
How it works
The rest of this article explains how the RegexValidationRule
and RegexValidator
work. You do not need to read this section in order to use those classes in your applications.
The RegexValidationRule
is very simple. Below is a stripped-down version of the class:
public class RegexValidationRule : ValidationRule
{
public override ValidationResult Validate( object value,
CultureInfo cultureInfo )
{
ValidationResult result = ValidationResult.ValidResult;
if( ! String.IsNullOrEmpty( this.RegexText ) )
{
string text = value as string ?? String.Empty;
if( ! Regex.IsMatch( text, this.RegexText, this.RegexOptions ) )
result = new ValidationResult( false, this.ErrorMessage );
}
return result;
}
The Validate
method (which is inherited from ValidationRule
) simply calls the static IsMatch
method of the Regex
class to perform the validation. If the input value does not match the regular expression stored in the RegexText
property, a ValidationResult
is returned which indicates that the value is invalid and supplies an error message. The error message is retrieved from the ErrorMessage
property. If the input value is valid, the ValidResult
property is returned. That property returns a singleton instance of ValidationResult
, so using it to indicate success allows us to avoid fragmenting the managed heap with redundant ValidationResult
instances that all express the same thing (�the value is valid�).
The RegexValidator
class is more complicated. First, let�s see how the class is declared:
public static class RegexValidator
{
}
Since it is a static class, it can never be instantiated and all of its members must be static. This makes sense, because this class is a service provider and all of its functionality is exposed via attached properties. There is no reason to instantiate the class.
Next, we will examine the implementation of its attached properties:
public static readonly DependencyProperty ErrorMessageProperty;
public static string GetErrorMessage( TextBox textBox )
{
return textBox.GetValue( ErrorMessageProperty ) as string;
}
public static void SetErrorMessage( TextBox textBox, string value )
{
textBox.SetValue( ErrorMessageProperty, value );
}
public static readonly DependencyProperty RegexTextProperty;
public static string GetRegexText( TextBox textBox )
{
return textBox.GetValue( RegexTextProperty ) as string;
}
public static void SetRegexText( TextBox textBox, string value )
{
textBox.SetValue( RegexTextProperty, value );
}
Those attached properties are following the required convention of having public static Get/Set methods to associate a property value with a DependencyObject
(in this case, it only works with the TextBox
class). The attached properties are registered in a static constructor:
static RegexValidator()
{
RegexTextProperty = DependencyProperty.RegisterAttached(
"RegexText",
typeof( string ),
typeof( RegexValidator ),
new UIPropertyMetadata( null, OnAttachedPropertyChanged ) );
ErrorMessageProperty = DependencyProperty.RegisterAttached(
"ErrorMessage",
typeof( string ),
typeof( RegexValidator ),
new UIPropertyMetadata( null, OnAttachedPropertyChanged ) );
}
Notice that when the properties are registered, a callback method is supplied in the UIPropertyMetadata
argument. That callback is invoked whenever the value of one of RegexValidator
�s attached properties is modified for a TextBox
. That callback method is implemented like this:
static void OnAttachedPropertyChanged( DependencyObject depObj,
DependencyPropertyChangedEventArgs e )
{
TextBox textBox = depObj as TextBox;
if( textBox == null )
throw new InvalidOperationException(
"The RegexValidator can only be used with a TextBox." );
VerifyRegexValidationRule( textBox );
}
You might be wondering why I bothered using a callback to call the VerifyRegexValidationRule
method, instead of just calling that method from within the static Set
methods created for the attached properties. It might seem that this approach would work just fine, and be simpler:
public static void SetErrorMessage( TextBox textBox, string value )
{
textBox.SetValue( ErrorMessageProperty, value );
VerifyRegexValidationRule( textBox );
}
This approach would work if the value of the ErrorMessage
property was always set by calling RegexValidator.SetErrorMessage
for a TextBox
. However, it is entirely possible to set the ErrorMessage
attached property (or any other attached property) for a TextBox
by directly calling SetValue
on the TextBox
itself, just like the SetErrorMessage
method does. In that situation, the call to VerifyRegexValidationRule
would not occur and the TextBox
�s Text
property binding would never have a RegexValidationRule
added to its ValidationRules
collection. By using the callback method supplied when registering the attached property, we can rest assured that whenever the attached property value is modified for a TextBox
, our callback will call VerifyRegexValidationRule
.
Now, it is time to examine what the VerifyRegexValidationRule
method does. Remember, this method is called whenever the value of a RegexValidator
attached property is modified.
static void VerifyRegexValidationRule( TextBox textBox )
{
RegexValidationRule regexRule = GetRegexValidationRuleForTextBox( textBox );
if( regexRule != null )
{
regexRule.RegexText =
textBox.GetValue( RegexValidator.RegexTextProperty ) as string;
regexRule.ErrorMessage =
textBox.GetValue( RegexValidator.ErrorMessageProperty ) as string;
}
}
This method attempts to retrieve a RegexValidationRule
which is to be used for the specified TextBox
�s Text
property binding. If it gets a reference to one, it transfers property values to that object. Notice that the actual values for the RegexValidationRule
properties are stored by the TextBox
as attached properties. When working with attached properties, it is important to keep in mind that the value of those properties are stored by the object to which they are applied, not by the class/object which exposes the attached properties.
At this point, it is necessary to examine how the GetRegexValidationRuleForTextBox
method works. This method is responsible for creating or retrieving a RegexValidationRule
object for a given TextBox
.
static RegexValidationRule GetRegexValidationRuleForTextBox( TextBox textBox )
{
if( ! textBox.IsInitialized )
{
EventHandler callback = null;
callback = delegate
{
textBox.Initialized -= callback;
VerifyRegexValidationRule( textBox );
};
textBox.Initialized += callback;
return null;
}
BindingExpression expression =
textBox.GetBindingExpression( TextBox.TextProperty );
if( expression == null )
throw new InvalidOperationException(
"The TextBox's Text property must be bound for the RegexValidator to " +
"validate it." );
Binding binding = expression.ParentBinding;
if( binding == null )
throw new ApplicationException(
"Unexpected situation: the TextBox.Text binding expression has no " +
"parent binding." );
RegexValidationRule regexRule = null;
foreach( ValidationRule rule in binding.ValidationRules )
{
if( rule is RegexValidationRule )
{
if( regexRule == null )
regexRule = rule as RegexValidationRule;
else
throw new InvalidOperationException(
"There should not be more than one RegexValidationRule in a Binding's " +
"ValidationRules." );
}
}
if( regexRule == null )
{
regexRule = new RegexValidationRule();
binding.ValidationRules.Add( regexRule );
}
return regexRule;
}
That method gets a reference to the BindingExpression
associated with the TextBox
�s Text
property. It then retrieves a reference to the Binding
which owns the expression, and iterates the Binding
�s ValidationRules
collection looking for a RegexValidationRule
. If it cannot find one, it will create a new instance and add it to the ValidationRules
collection. If it finds more than one RegexValidationRule
, it will throw an exception, because there is no way to know which one should be returned.
Conclusion
The RegexValidationRule
and RegexValidator
provide support for validating a TextBox
in WPF, using regular expressions.
You can create a RegexValidationRule
and explicitly add it to the Text
property�s Binding
, or simply use the RegexValidator
�s attached properties and it will take care of the work for you.
If you need to specify a single RegexOptions
value for the RegexValidationRule
, you can use the RegexOptions
property of that class in XAML.
When creating regular expressions in a XAML file, keep in mind that XAML requires that certain characters must be escaped (such as the ampersand and apostrophe).
The complete source code and a demo application can be downloaded from the top of this article.
Revision History
- 17 September 2006 - Created article.
- 2 November 2006 - Originally, the article stated that bitwise operations cannot be performed in XAML (i.e., the XAML parser doesn't support them). I mentioned that "fact" in regards to combining multiple values of the
RegexOptions
enum. It turns out that I was wrong; flagged enum values can be combined by separating them with a comma. I removed that misinformation from the article. Here's the WPF forum post which revealed the truth about combining flagged enums in XAML: Microsoft Forums.