Tutorials Forums
     Tutorials Videos
        Sign Up Now For FREE
Welcome, Guest
Username Password: Remember me

Regex Validation in WPF
(1 viewing) (1) Guest
  • Page:
  • 1

TOPIC: Regex Validation in WPF

Regex Validation in WPF 10 Feb 2010 15:29 #272

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.

Full Size Image
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,. 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 );


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
{
// All other members were omitted for clarity.
 
 
/// <summary>
 
/// Validates the 'value' argument using the regular expression and
 
/// RegexOptions associated with this object.
 
/// </summary>
 
public override ValidationResult Validate( object value,
CultureInfo cultureInfo )
{
ValidationResult result = ValidationResult.ValidResult;
 
// If there is no regular expression to evaluate,
 
// then the data is considered to be valid.
 
if( ! String.IsNullOrEmpty( this.RegexText ) )
{
// Cast the input value to a string (null becomes empty string).
 
string text = value as string ?? String.Empty;
 
// If the string does not match the regex, return a value
 
// which indicates failure and provide an error mesasge.
 
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
{
// There's more to come...
 
}

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:
/// <summary>
 
/// Identifies the RegexValidator's ErrorMessage attached property.
 
/// This field is read-only.
 
/// </summary>
 
public static readonly DependencyProperty ErrorMessageProperty;
 
/// <summary>
 
/// Returns the error message used when validation fails for the
 
/// specified TextBox.
 
/// </summary>
 
/// <param name="textBox">The TextBox whose error message is returned.</param>
 
public static string GetErrorMessage( TextBox textBox )
{
return textBox.GetValue( ErrorMessageProperty ) as string;
}
 
/// <summary>
 
/// Sets the error message used when validation fails for the
 
/// specified TextBox.
 
/// </summary>
 
/// <param name="textBox">The TextBox being validated.</param>
 
/// <param name="value">The error message.</param>
 
public static void SetErrorMessage( TextBox textBox, string value )
{
textBox.SetValue( ErrorMessageProperty, value );
}
 
/// <summary>
 
/// Identifies the RegexValidator's RegexText attached property.
 
/// This field is read-only.
 
/// </summary>
 
public static readonly DependencyProperty RegexTextProperty;
 
/// <summary>
 
/// Returns the regular expression used to validate the specified TextBox.
 
/// </summary>
 
/// <param name="textBox">
 
/// The TextBox whose regular expression is returned.
 
/// </param>
 
public static string GetRegexText( TextBox textBox )
{
return textBox.GetValue( RegexTextProperty ) as string;
}
 
/// <summary>
 
/// Sets the regular expression used to validate the
 
/// specified TextBox.
 
/// </summary>
 
/// <param name="textBox">The TextBox being validated.</param>
 
/// <param name="value">The regular expression.</param>
 
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
/// <summary>
 
/// Invoked whenever an attached property of the
 
/// RegexValidator is modified for a TextBox.
 
/// </summary>
 
/// <param name="depObj">A TextBox.</param>
 
/// <param name="e"></param>
 
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:
// This is not how it works!
 
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.
/// <summary>
 
/// Creates or modifies the RegexValidationRule in the TextBox's
 
/// Text property binding to use the current values of the attached
 
/// properties exposed by this class.
 
/// </summary>
 
/// <param name="textBox">The TextBox being validated.</param>
 
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.
/// <summary>
 
/// Returns a RegexValidationRule to be used for validating the specified
 
/// TextBox. If the TextBox is not yet initialized, this method returns null.
 
/// </summary>
 
static RegexValidationRule GetRegexValidationRuleForTextBox( TextBox textBox )
{
if( ! textBox.IsInitialized )
{
// If the TextBox.Text property is bound, but the TextBox is not yet
 
// initialized, the property's binding can be null. In that situation,
 
// hook its Initialized event and verify the validation rule again when
 
// that event fires. At that point in time, the Text property's binding
 
// will be non-null.
 
EventHandler callback = null;
callback = delegate
{
textBox.Initialized -= callback;
VerifyRegexValidationRule( textBox );
};
textBox.Initialized += callback;
return null;
}
 
// Get the binding expression associated with the TextBox's Text property.
 
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." );
 
// Get the binding which owns the binding expression.
 
Binding binding = expression.ParentBinding;
if( binding == null )
throw new ApplicationException(
"Unexpected situation: the TextBox.Text binding expression has no " +
"parent binding." );
 
// Look for an existing instance of the RegexValidationRule class in the
 
// binding. If there is more than one instance in the ValidationRules
 
// then throw an exception because we don't know which one to modify.
 
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 the TextBox.Text property's binding does not yet have an
 
// instance of RegexValidationRule in its ValidationRules,
 
// add an instance now and return it.
 
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.


This attachment is hidden for guests. Please log in or register to see it.
This attachment is hidden for guests. Please log in or register to see it.
Attachments:
  • Attachment This attachment is hidden for guests. Please log in or register to see it.
  • Attachment This attachment is hidden for guests. Please log in or register to see it.
  • CE
  • OFFLINE
  • Administrator
  • Posts: 197
  • Karma: 78
CodersEngine
  • Page:
  • 1
Moderators: mnjon
Time to create page: 1.11 seconds