Daily Archives: March 7, 2012

Validation in WPF 1: Error reporting with a MessageBox

In the last couple of posts (here and here), we explored some of the aspects of data binding in WPF. The little application we wrote to illustrate data binding allowed the user to step through the integers, testing each one to see if it is prime or perfect. The user could also enter a number in a TextBox directly.

One bit of unfinished business was that the application didn’t deal with erroneous input correctly. WPF provides a validation mechanism which allows checking of user input to be done in a separate class.

Unfortunately, Expression Blend offers no facility for adding validation to an input control, so we need to edit the code by hand. It is possible to write validation code in either XAML or C#, but since I loathe writing XAML, and since we need to write some of the code in C# no matter what we do, I’ll take the path of writing all the binding and validation code for the TextBox’s Text property in C#.

If you’re starting with the project in the state arrived at in the last post, you’ll need to remove the binding for the Text property from the XAML line for numberTextBox. That is, you’ll need to remove the Text=”{Binding Number}” bit from that line. Alternatively, you can just download the code for this post from the link at the bottom, and follow along.

We now need to handle the data binding of the Number TextBox entirely within the C# code. Before we write the code, however, we’ll describe the process of validation.

When we bound the text in the TextBox to a data field in the PrimePerfect class, we (well, WPF, actually) did a conversion of the text in the TextBox to an int data type in the data field. If the user types something in the TextBox that doesn’t parse as an int, an exception will be thrown. The validation process catches this exception and can then generate a validation error, for which we can add an event handler.

Validation can be attached to a binding by adding a ValidationRule object to a binding’s ValidationRules collection. ValidationRule is an abstract class in the System.Windows.Controls namespace, so if we want to write our own validation rule, we need to inherit this class and implement its one method, which is called Validate().

However, a quick and dirty option is to use the ExceptionValidationRule class provided by WPF. This class implements ValidationRule, and all it does is forward the error message provided by the exception. For many applications this is enough, since exception error messages will usually tell you what’s gone wrong. However, if you want a nice user interface, or if you want to place other restrictions on the input that wouldn’t throw exceptions, you’ll need to provide your own custom ValidationRule implementation.

First, we’ll look at how to use ExceptionValidationRule, since this case includes most of the steps you’ll need for a custom validaton rule anyway. The code is contained in the constructor for MainWindow, which is in the MainWindow.xaml.cs code-behind file in the project.

    public MainWindow()
      primePerfect = new PrimePerfect();
      baseGrid.DataContext = primePerfect;
      Binding numberTextBinding = new Binding();
      numberTextBinding.Path = new PropertyPath("Number");
      numberTextBinding.ValidationRules.Add(new ExceptionValidationRule());
      numberTextBinding.NotifyOnValidationError = true;
      Validation.AddErrorHandler(numberTextBox, numberTextBox_ValidationError);
      BindingOperations.SetBinding(numberTextBox, TextBox.TextProperty, numberTextBinding);

Lines 4 and 5 create a PrimePerfect object and define it as the data context, as we described in the earlier post on data binding. Starting at line 6, we see how to define a data binding in C# as opposed to XAML. On line 6 we create an empty Binding object, and on line 7 we set its path to “Number”. This tells the binding to look for a data element named “Number” in the data context. On line 8, we add an ExceptionValidationRule object to this binding’s ValidationRules collection.

One peculiarity of validation is that a validation error event is not generated unless you explicitly ask for it. Line 9 does this by setting the binding’s NotifyOnValidationError property to ‘true’. If you set this property to ‘false’, this effectively turns off validation for this binding.

Line 10 adds an event handler for validation errors; in this case the handler is numberTextBox_ValidationError, which we’ll look at in a second. Notice that this is done by calling the static method AddErrorHandler in the Validation class, and it links the TextBox (not the individual binding!) with the error handler. This means that all validation errors for the TextBox will be directed to this handler. Usually this isn’t a problem, since most simple controls have only one place where the user can enter data, but in some cases you’ll need to sort out which part of the control is generating the validation error.

Finally, on line 11, connect the binding to the numberTextBox. Note that the second argument to SetBinding() is the property of the TextBox to which the binding is to be applied. This property must be a dependency property, which means it’s a property that can be set through various methods such as defining a style, or data binding. (It’s a bit of a circular definition, since we’re saying that data can be bound only to a dependency property, and a dependency property is a property to which data can be bound. Duh… Really, each control has to define which of its properties are dependency properties, and in practice, these are all the properties that you’d ever want to define through data binding.)

So this defines a binding and adds a validation error checker to it. The only remaining thing is to look at the validation error handler:

    void numberTextBox_ValidationError(object sender, ValidationErrorEventArgs e)
      if (e.Action == ValidationErrorEventAction.Added)
        MessageBox.Show((string)e.Error.ErrorContent, "Incorrect number or format");

This code displays a MessageBox containing an error message when a validation error occurs. However, it’s not quite that simple, as you can see from the ‘if’ statement (thanks to Stuart Birse for pointing this bit out). A validation error generates an event twice. The first time is when the error itself occurs (when the user enters some invalid data). The second time is when the error is cleared (for example, by the user deleting the incorrect data in the TextBox and entering some correct data). Both these events are directed to the same handler, so you need to check which type of event has occurred, and handle it appropriately. If you want to handle the case where the validation error has first occurred (the most common case), you need to check the Action field of the ValidationErrorEventArgs parameter and see if it’s equal to ValidationErrorEventAction.Added. We’ve done that here, and the result is a MessageBox displaying the error message, which is contained in the Error.ErrorContent property of the ValidationErrorEventArgs parameter. If you do need to do something when the validation error is cleared, you can check if the Action field of the ValidationErrorEventArgs parameter is equal to ValidationErrorEventAction.Removed. Note that if you don’t have this check in your event handler, you’ll get the MessageBox showing up both when the user enters incorrect data, and when the user clears this data and enters some correct data, which is most likely not what you want to happen.

It does seem a bit daft that these two events are handled by the same handler, since they are logically distinct, but I guess that’s just one of the foibles of WPF.

Runnning the program as it stands will now generate a validation error when the user enters something into the TextBox that can’t be parsed as an int, since that will throw an exception. However, we’d also like to prevent the user from entering integers less than 2. For that we need a custom validation rule.

Writing such a rule is quite simple. We define a class that inherits ValidationRule, and write the logic in the Validate() method. The code in the sample is as follows.

  using System.Windows.Controls;
  class NumberRangeRule : ValidationRule
    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
      int number;
      if (!int.TryParse((string)value, out number))
        return new ValidationResult(false, "Must enter an integer >= 2");
      if (number < 2)
        return new ValidationResult(false, "Number must be >= 2");
      return ValidationResult.ValidResult;

Note we need to access System.Windows.Controls to use the ValidationRule class. The logic in Validate() is pretty straightforward. We first try to parse the ‘value’ (the text in the TextBox in this case). If this fails, the TryParse() method returns false, and we return a ValidationResult object. The first argument to the constructor is a bool value that determines if a validation error is thrown. If its value is ‘false’, an error has occurred. The second parameter is the error message that will show up in the Error.ErrorContent ValidationErrorEventArgs parameter above.

If TryParse succeeds, the result of the parse is stored in ‘number’, and we then test the value of ‘number’ to see if it’s less than 2. If so, we generate another error, this time because the number is outside the required range. Note that this error would not throw an exception, so we need a custom validation rule to catch it.

Finally, if the data is valid we return the built in ValidationResult.ValidResult, which does not throw a validation error, so the error handler will not be called.

To use this custom rule, we merely replace line 8 in the first listing above with:

      numberTextBinding.ValidationRules.Add(new NumberRangeRule());

Everything will now work as before, except the error messages are generated by the custom rule.

The MessageBox provides accurate feedback if the user enters incorrect data, but it’s not very elegant. There are nicer ways of using validation to provide error messages, and we’ll look at those in a later post.

Code for this post available here.