Introduction
Since the dawn of software development, the single biggest threat to reliable information has been end-users. After decades of the existence of computers, why do we still need to inform users that "the Start Date should always be less than the End Date", or that "First Name is a required field"? Since User.Current.Electrocute()
won't be added to the .NET Framework until .NET 4.0, most of us generally have to resort to displaying some kind of UI cues to politely tell the user what they've done wrong, and how they can fix it.
Under Windows Forms, we had the ErrorProvider
, and under ASP.NET, we had Validator
controls. In Windows Presentation Foundation, the approach is going to change, but the goal remains the same: telling users that something is wrong. In this article, I'll discuss how input validation is designed to be handled in Windows Presentation Foundation (hereafter referred to as WPF). Specifically, I'll discuss the ExceptionValidationRule
class, creating custom ValidationRule
s, how to display errors, when to display errors (UpdateSourceTrigger
s), and finally, an alternative approach using an implementation of the Windows Forms ErrorProvider
for WPF.
Table of Contents
- Welcome to WPF Validation
- Very Simple Validation: ExceptionValidationRule
- Displaying Errors
- Customized ValidationRules
- Controlling When to Validate: UpdateSourceTriggers
- Where Does an ErrorProvider Fit in?
- IDataErrorInfo
- Creating our ErrorProvider
- Conclusion
- Special Thanks
Welcome to WPF Validation
If you never spent much time with Windows Forms or data binding, I'm hoping that this article will remain simple enough to follow along. Validation in Windows Presentation Foundation takes an approach very similar to ASP.NET validation, in that most "business rules" are enforced on the user interface and applied to specific controls and bindings. This approach is quite simple to understand and implement, but some proponents of "rich domain models" and object-oriented designs (including myself) have some problems with it. As we approach the end of the article, I'll discuss why this is and a way to do things differently, but still leveraging some of the strengths of WPF.
On the other hand, if you did spend a lot of time using Windows Forms, and made heavy use of the ErrorProvider
/IDataErrorInfo
approach to validation, you might be disheartened to know that there isn't one in Windows Presentation Foundation. Luckily, since I too was disappointed about having to enforce everything "on the UI", towards the end of the article, I'll show how to create an ErrorProvider for use in your Windows Presentation Foundation applications. However, even if you're convinced that validating on the UI is a Bad Idea (TM), I'd encourage you to read the full article as I'll discuss some of the other validation features in WPF that you can still leverage.
Warning: All of the sample code was written using the BETA 2 release of .NET 3.0, and subsequently, some of it may have changed. Hopefully, the concepts will stay the same though, otherwise this will be a very wasted article :)
Very Simple Validation: ExceptionValidationRule
Most of the time when we talk about validation, we're talking about validating user input (other kinds of validation are outside the scope of this article). Let's look at the most simple kind of built-in validation offered by WPF - the ExceptionValidationRule
. To begin our example, let's create a simple Customer
class:
public class Customer
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}
Writing classes is fun, but you'll probably never convince people to pay you unless you can add some kind of UI to let them interact with your classes. Since this article is about Windows Presentation Foundation, let's use XAML to create our UI. As we're smart developers, we'll also use data binding:
<TextBox Text="{Binding Path=Name}" />
Before we go any further, it's important to note that the odd-looking markup above is actually just a kind of shorthand for writing this:
<TextBox>
<TextBox.Text>
<Binding Path="Name" />
</TextBox.Text>
</TextBox>
Now, let's say one of your requirements is that customer names are mandatory. To implement this constraint, you could change your customer's Name
property to look like this:
public string Name
{
get { return _name; }
set
{
_name = value;
if (String.IsNullOrEmpty(value))
{
throw new ApplicationException("Customer name is mandatory.");
}
}
}
Using WPF Validation Rules on our data binding, we can display this error automatically. All we need to do is make use of the ValidationRules
property on our binding, like this:
<TextBox>
<TextBox.Text>
<Binding Path="Name">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
If you ran this code, you'd see something which looks like this:
Displaying Errors
Windows Presentation Foundation has a static class inside the System.Windows.Controls
namespace, imaginatively named "Validation
". It has a number of static dependency properties which you can apply to any control. The ones we're most interested in are:
Errors
- A list of error messages that apply to the bindings on this control.
HasError
- Indicates whether there are any errors in the Errors
property.
ErrorTemplate
- A ControlTemplate
we can apply if there is an error.
By default, the Validation
class uses an ErrorTemplate
that has a red border around the control, which is why we get a red border in the program above. If the default red borders aren't your thing, I'll understand. You might want thick green borders with orange exclamation marks:
To achieve the look you're after, you could define your own control template:
<Application.Resources>
<ControlTemplate x:Key="TextBoxErrorTemplate">
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right"
Foreground="Orange"
FontSize="12pt">!!!!</TextBlock>
<Border BorderBrush="Green" BorderThickness="1">
<AdornedElementPlaceholder />
</Border>
</DockPanel>
</ControlTemplate>
</Application.Resources>
The AdornerElementPlaceholder
is used to say "put the invalid control here". Once you've created this template, you can then reuse it on your customer name TextBox
by setting the Validation.ErrorTemplate
attached property:
<TextBox
Validation.ErrorTemplate="{StaticResource TextBoxErrorTemplate}">
[...]
<TextBox>
Or, to save having to set the ErrorTemplate
every time, you could do it in a WPF Style
. If we were to set the style's TargetType
to TextBox
, and don't use a key, all text boxes in our application will automatically receive this style:
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right"
Foreground="Orange"
FontSize="12pt">
!!!!
</TextBlock>
<Border BorderBrush="Green" BorderThickness="1">
<AdornedElementPlaceholder />
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now, green borders are all well and good, but they don't exactly tell the users what they've done wrong. Since we included the error message in the exception we were throwing earlier, we can make use of the static Validation.Errors
attached property to get this value and use it as a property on our TextBox
. The most common example is to set the ToolTip
, like so:
<Style TargetType="{x:Type TextBox}">
[... SNIP: The code from above ...]
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
Now, if we mouse over the TextBox
while it's in an error state, we'll get a tool-tip telling us what we did wrong:
All we need to do is place that style in our application resources, ensure we apply any validation rules to our bindings, and voila!
On a more advanced note, if you would rather show the errors somewhere else than in the ToolTip
, such as in the TextBlock
where the orange exclamation marks were, you could use a style like this:
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right"
Foreground="Orange"
FontSize="12pt"
Text="{Binding ElementName=MyAdorner,
Path=AdornedElement.(Validation.Errors)
[0].ErrorContent}">
</TextBlock>
<Border BorderBrush="Green" BorderThickness="1">
<AdornedElementPlaceholder Name="MyAdorner" />
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Customized Validation Rules
I know what you're thinking: "Do I have to throw exceptions?"
I'm glad you asked. If you remember our customer's Name
property above, you'll remember we were throwing an exception in order to display the errors using an ExceptionValidationRule
. I'm sure most people will agree that throwing exceptions isn't really the optimal way of signaling that we have user input errors. Apart from the performance penalties, personally, I believe that "user input" errors aren't exactly "exceptional", and so they are not exactly what exceptions were designed for.
If you hunt around the framework, you'll see that the ExceptionValidationRule
class inherits from an abstract ValidationRule
class. The ValidationRule
class looks like this:
public abstract class ValidationRule
{
public abstract ValidationResult Validate(
object value,
CultureInfo culture);
}
When we created our binding earlier, I had some XAML that resembled this:
<Binding Path="Name">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
The truth is, we could have used anything that inherits from the ValidationRule
base class. Let's create a custom ValidationRule
designed just to validate the lengths of strings.
namespace MyValidators
{
public class StringRangeValidationRule : ValidationRule
{
private int _minimumLength = -1;
private int _maximumLength = -1;
private string _errorMessage;
public int MinimumLength
{
get { return _minimumLength; }
set { _minimumLength = value; }
}
public int MaximumLength
{
get { return _maximumLength; }
set { _maximumLength = value; }
}
public string ErrorMessage
{
get { return _errorMessage; }
set { _errorMessage = value; }
}
public override ValidationResult Validate(object value,
CultureInfo cultureInfo)
{
ValidationResult result = new ValidationResult(true, null);
string inputString = (value ?? string.Empty).ToString();
if (inputString.Length < this.MinimumLength ||
(this.MaximumLength > 0 &&
inputString.Length > this.MaximumLength))
{
result = new ValidationResult(false, this.ErrorMessage);
}
return result;
}
}
}
Our validator has three properties: a MinimumLength
, a MaximumLength
, and an ErrorMessage
string which is shown if the value doesn't fit within the range. We're now ready to use it in our XAML.
First, we'll need to reference it in our XML namespaces at the top of the file:
<Window [...]
xmlns:validators="clr-namespace:MyValidators" />
Now we can add it to our binding's ValidationRules
:
<Binding Path="Name">
<Binding.ValidationRules>
<validators:StringRangeValidationRule
MinimumLength="1"
ErrorMessage="A name is required." />
</Binding.ValidationRules>
</Binding>
If you've added the styles I gave above, that should be all you need to do to show validation messages.
Controlling When to Validate: UpdateSourceTriggers
If you ran any of the sample code above, you'll notice that the validation only takes place when you hit Tab or click away from the text box. That's a good default for most cases, but if you'd like validation to take place at other times, you can use the UpdateSourceTrigger
property on your binding.
The UpdateSourceTrigger
property can take one of three possible values:
- The default is
LostFocus
, which means that the bound data source will be updated and validated when the UI element loses focus (such as when you tab away, or click on another element).
- The second option is
PropertyChanged
, which occurs when the property you are binding to (the TextBox
's Text
property in the example above) is changed. This enables you to validate whenever the user types or changes the value, without having to wait until they tab away (this is great for filtering a list of things).
- Finally, there's the
Explicit
option, which only happens when you tell it to happen.
To make use of the UpdateSourceTrigger
property in our code, all we'd need to do is set it in our binding:
<TextBox>
<TextBox.Text>
<Binding Path="Name" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
For more information on UpdateSourceTriggers
, see the MSDN page for the UpdateSourceTrigger enumeration.
Where Does an ErrorProvider Fit in?
We've already discussed that the ExceptionValidationRule
isn't a nice way of validating. Some of the problems you may have with creating custom validation rules as I showed above are:
- Our business "rules" are defined in markup, hidden inside our bindings, and can be hard to locate and maintain, as well as difficult to reuse.
- We can't easily get a "list" of all of the broken rules on a form in order to perform other logic - such as determining if the customer can be saved when the "Save" button is clicked.
Defining business rules in markup may not be such a problem for some applications, but in applications with rich business objects such as Trial Balance, or applications using rich frameworks such as CSLA, or in cases where the rules are shared across many screens or applications, you may find this to be a big limitation. If this isn't a problem for you, you might not get much out of the rest of this article.
This means that the ValidationRules approach isn't very useful to some of us. However, all is not lost, as we can still make use of the static Validation
class, and of its use of styles and control templates to control the display of errors.
IDataErrorInfo
The IDataErrorInfo
interface has been kicking around since .NET 1.1, and was used heavily in DataSet
s as well as used by many business object frameworks. You'll find this gem in the System.ComponentModel
namespace. If you've been reading my blog for some time, hopefully this interface isn't a stranger to you.
IDataErrorInfo
was designed for reporting errors to user interface controls from the objects they are bound to. The DataGrid
(1.1) and DataGridView
(2.0) in Windows Forms both detected the presence of this interface on objects they were bound to automatically, and showed any errors without any work. The Windows Forms ErrorProvider
could automatically be used to display errors on any control that came from the objects they (and the ErrorProvider
) were bound to, all without any extra code being written.
To refresh your memory, this is how we would have used this interface in .NET 2.0:
public class Customer : IDataErrorInfo
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
public string Error
{
get
{
return this[string.Empty];
}
}
public string this[string propertyName]
{
get
{
string result = string.Empty;
propertyName = propertyName ?? string.Empty;
if (propertyName == string.Empty || propertyName == "Name")
{
if (string.IsNullOrEmpty(this.Name))
{
result = "Name cannot be blank!";
}
}
return result;
}
}
}
Back then, all we had to do was bind a grid to our Customer
object, and any errors would be automatically reported. If we bound the Customer
's Name
to a TextBox
, we just had to drag an ErrorProvider
onto the form, set its DataSource
to our Customer
object, and all of our validation was done for us.
Sadly, IDataErrorInfo
appears to have been benched in WPF, as there doesn't seem to be any built-in support for it. This means that there is no ErrorProvider
component in WPF either. Since the concept of an ErrorProvider
is pretty simple, let's have a go at making one ourselves.
More on IDataErrorInfo...
The MSDN entry for IDataErrorInfo is a good starting point. For a deeper discussion of the IDataErrorInfo
interface and a better way to implement business object validation, you might enjoy my recent Delegates and Business Objects article on the CodeProject. Rocky Lhotkas' brilliant CSLA architecture also makes extensive use of the IDataErrorInfo
interface.
Creating our ErrorProvider
The ErrorProvider
that I have built (found in the attached sample code) inherits from the WPF Decorator
class, which means you can "put stuff inside it". To use it, you just need to do something like this:
<validators:ErrorProvider>
<StackPanel>
<TextBox Text="{Binding Path=Name}" />
<TextBox Text="{Binding Path=Age}" />
</StackPanel>
</validators:ErrorProvider>
My ErrorProvider
works by cycling through all of the controls that are inside of it, and looking for any data bindings on their properties. When a value of one of the bound properties changes, it checks if its DataContext
implements IDataErrorInfo
, and if so, it gets any error messages, and displays them using the built-in static Validation
class. That means you can use the styles and control templates that I showed above, whilst keeping all of your validation logic in another class.
Cycling through the control hierarchy in WPF is done using the LogicalTreeHelper
class. The code itself is a little long to post here, but is included in the sample code download at the top of the page.
Using the WPF ErrorProvider
brings a number of benefits:
- Your XAML is much more compact, as you don't need to add a list of
ValidationRule
s to each binding.
- All of your validation can be done by your business objects, rather than at the UI. This lets you have great encapsulation on your objects while still having an informative UI.
- You can call the
Validate()
method on the ErrorProvider
to force validation, and check if the controls are valid, rather than having to inspect each one.
- You can use the
GetFirstInvalidElement()
method to get the first control with an error on your form, to set focus to it easily. This is great if, for example, your "Save" button has been clicked but you want to show them that they still have errors.
Conclusion
Whether you choose to use custom ValidationRule
s, my ErrorProvider
, or simply throw exceptions, I hope I have given you enough information to make an educated decision that suits your project.
If your applications have a very rich domain model, you may find the ErrorProvider
approach helpful. If your application is very service oriented, or very UI centric, ValidationRule
s might be all you need.
Of course, if none of these approaches suit your requirements, you could always hold out for .NET 4.0 and the introduction of User.Current.Electrocute()
:)
Special Thanks
I would like to give a special thanks to Paul Czywcynski of TempWorks who has done a lot of testing with my ErrorProvider
and given me a couple of bug fixes to make it work in a few places that I hadn't tried.
I'd also like to thank Mike Brown, who pointed me to the LogicalTreeHelper
and also pointed me to ValidationRules
(of course, he didn't mention them until after I'd done my ErrorProvider
:) ).