Introduction

In this article, I have discussed the validation process in WPF. For understanding the validation process, I have created a simple application which divides numbers entered in text boxes and displays the result in a label. If the user enters invalid data, it displays the appropriate error messages.

Background

While dividing two numbers, the most likely issues can be related to handling of non-numeric data and dividing by zero. In this article, I have described how to do data validation for such errors. In this application, I have validated that the user does not enter non-numeric data and zero in the textboxes. I have developed the application using Microsoft Visual C# 2010 Express Edition (Microsoft .NET Framework Version 4.0.21006).

Using the Code

To use the validator function, we must first declare the namespace where it will be found. This is done by adding an attribute for the root Window element as follows:

The above code displays the result of division by binding the Division property to a TextBlock. To display error if any validation error occurs, I have created a control template with a key called errorTemplate as follows:

In the above code, the AdornedElementPlaceholder element is used to display the error message. The user interface to accept two numbers from the user and display the result of division is created as follows:

In the above code, the Canvas element is used as a container. Two TextBoxes t1 and t2 are bound to the properties FirstNumber and SecondNumber respectively. The error templates for the two textboxes are specified using the Validation.ErrorTemplate attribute.

The <Binding> child element of the textbox is used to bind the textbox to its corresponding data property (FirstNumber and SecondNumber). The custom validation class is specified by the <local:NumberValidator/> element. The Button b1 is used to divide the numbers and the result is displayed in a label called lblResult. The template for the label is specified using the ContentTemplate attribute.

The problem with error template is that it always displays a generic error message. To display specific error as a tooltip, the Validation.Error attribute is used. The Validation.Error attribute is used to link the textboxes to the error function called NumberError. The code-behind code for the NumberError function is as follows:

The custom validation class must inherit from the ValidationRule class and it must override the Validate method. The Validate method takes two parameters. The first parameter is the object to be validated. The second parameter is of the type System.Globalization.CultureInfo. In this case, the Validate function checks whether the value is numeric and non-zero and returns a ValidationResult object. The first parameter of the ValidationResult constructor is boolean and indicates that the data is valid if it is true and invalid otherwise. The second parameter is the error message string in case of invalid data and null otherwise.

Points of Interest

I hope that this article will help in understanding the basics of WPF validation in a simple way.

This is one way of doing validation in WPF, but you don't mention the more common approach of IDataErrorInfo

Also most people want to know how to do validations from their ViewModels. This doesn't show that. There is also a newer idea available in SL which is called INotifyDataErrorInfo, which you can use in WPF if you know what you are doing can be bothered, which pushes validation errors from VM to View.

In fact I just pushed a zip about this to my dropbox account today if you are interested :

I think it is worth nudging this to mention that Sascha is right about view models - I think most people who use WPF in any kind of real-world application use MVVM and therefore have view models of some kind, it would be a useful addition to this article to add some comments on how validation can work for MVVM orientated projects.

Validation in WPF has always been something which to me has felt a lot less confident than some of the other areas, I've always had a feeling that to get a red box around an edit control is easy but to handle real-world situations the system was less well thought out. I'll be interested to read over these

I found your small validation demo very useful for the application I am working on. So Thanks!
There is a small hitch you might know a solution for off the top of your head. When I instantiate my validator in my ViewModel, the property HasErrors is initially false. However if I call Validate() right after initializing, HasErrors would become true, but my View would no longer display the error template around my text/combo boxes. I don't understand why that would be. Do you know?

Sacha, I appreciate you finding time for this. On monday (when I'm back at work) I'll try your suggestion and let you know.

Hmm, I am trying to Validate in the ViewModels constructor - the Window/UserControl has already been initialised. The issue is, I have a button that is enabled based on whether there are errors, and I'm also trying to get the error template to show around text/combo boxes straight away. For both reasons I want to call Validate as soon as possible. (Just thinking out loud) On your solution, what could I do if the ControlContent is loaded before the DataContext is set? I'm guessing in that scenario I should be able to simply call Validate() in the ViewModels constructor.
I'll let you know how I go. And thanks again.

I've called Validate when the ContentControl has finished loading, however it has not entirely solved the problem. The button that is enabled based on whether HasErrors is false is now disabled at startup, however the error template is not displayed around the textbox that requires a value. When I add a value in it then clear it again the red outline is displayed. Is there a way to get the error template to display from the outset? I would've thought Validate on Loaded would've achieved that as that is all the textbox is doing in it's set accessor.

Edit: I think the reason was because it was inside an expander. When the expander is already expanded it works as expected. I dunno why, but at least that's my main problem solved.