Using Custom Validation Rules in WPF
Anyone who has ever developed data driven applications of any sort has had to deal with validation. Let’s face it, users make mistakes, even if they are developers! Validation can be a real pain to implement, and contrary to all our HelloWorld style applications, MessageBox.Show is not the best way to inform our users that we have an issue with their keyboarding skills.
I remember being ecstatic when I learned about the WinForms ErrorProvider. This handy little approach finally gave us a nice way to alert the user of the problem without being too heavy handed in the UI. Where it failed, though, was in customization. First of all, it took a lot of code behind to manage the ErrorProvider: checking field values against business rules, updating the provider’s list of errors, or clearing it out when the status was OK ended up being a lot of code, especially when there were a lot of data fields. And then, beyond deciding whether or not the Error symbol would blink (I always hated the blinking exclamation point), there wasn’t a lot of pizzazz we could add. Of course, that’s all changed with WPF.
Getting Started
Before I go any further, you can find most of the basics in these articles:
- SDK – Data Validation in .NET 3.5
- CodeProject – Validation in Windows Presentation Foundation
- MSDN – ValidationRule class
- MSDN – How To Implement Binding Validation
I recommend reading these articles. The CodeProject article even links to a Project with a WPF version of ErrorProvider. I will be using a couple code samples from these articles.
I’m not going to harp too much on the details presented in these other articles. Instead, I’m going to layout what I did to get this working and hopefully give you enough information to get started.
Using IDataErrorInfo
When you first start looking into validating data in WPF you will no doubt run into IDataErrorInfo. This seems to be the jumping off point. Implementing the interface is pretty straightforward. Here is a typical example:
using System.ComponentModel; namespace ValidationRulesPlay.ViewModels { public class DataErrorInfoSample : ViewModelBase, IDataErrorInfo { private int _int1; private int _int2; public int Int1 { get { return _int1; } set { _int1 = value; FirePropertyChangedEvent("Int1"); } } public int Int2 { get { return _int2; } set { _int2 = value; FirePropertyChangedEvent("Int2"); } } #region IDataErrorInfo Members public string Error { get { return null; } } public string this[string columnName] { get { string result = null; switch (columnName) { case "Int1": if (Int1 > 9999999) result = "Int1 number cannot be greater than 9999999."; break; case "Int2": if (Int2 > 99) result = "Int2 number cannot be greater than 99."; break; default: break; } return result; } } #endregion } }
[NOTE: the class above inherits from ViewModelBase, a class I use to implement INotifyPropertyChanged.]
And then you add a piece to your Binding syntax in XAML to activate it:
<TextBox x:Name="Int1TextBox" ValidatesOnDataError="True" VerticalAlignment="Center" HorizontalAlignment="Center" MinWidth="50" Margin="0,0,6,0"/>
This works and will by default wrap the offending textbox with a red border. It’s not a bad experience for simple examples, but as I see it there are a couple of drawbacks.
First, IDataErrorInfo validated the data after the property has been set. So if I type in a value that is out of range, the target property is still invalidly set with the bad value. This means that I have to do extra work to correct the error. In other words, this approach lets me put bad data in that I must then take out. Wouldn’t it be better if it just didn’t go in in the first place? Second, this interface uses a custom indexer, which frankly just feels like magic. It’s a personal preference, and a fear of the unknown, but I just don’t like it.
My other real issue with it is that it ends up being a lot of repetitive code. Look at the example above and you’ll see two integer values that are both tested to be within a certain range. While the ranges are different, the action is the same. Of course, you could create a method, or better yet an Extension Method, to handle this situation. Maybe it’s because of the hard-coded nature of the validation messages, but it still doesn’t feel right to me.
Creating Custom Validation Rules
Wouldn’t it be better to have a reusable piece of code to handle these similar situations? One with no real hard coding? In .NET 3.5 SP1 we have just such an animal, the Custom Validation Rule.
ValidationRule is an abstract class that we can inherit from to create a custom validation rule class of our own. I could certainly create a custom rule class for each property in my DAL, and perhaps sometimes a custom class for a particular field would make sense, but many times all I need is a generalized validation rule.
I mentioned previously that IDataErrorInfo checks the validation after the property has been updated. This means if we attempt to insert a non-integer field with a non-integer value, the validity checking never occurs: instead, WPF swallows the error. Using a validation rule causes the validation to occur before the property is updated, so we can easily prevent such an occurrence and report it to the user. It does still allow the bad data into the TextBox, but that’s not necessarily a bad thing.
For this example, we need a validation rule that will report when the user attempts to insert a non-integer character. We also want to be able to specify a range of acceptable values. Additionally, we are going to add a few fields to help customize the messaging experience. Here is the complete class:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Controls; namespace CustomValidationRules { public class IntegerValidationRule : ValidationRule { private int _min = int.MinValue; private int _max = int.MaxValue; private string _fieldName = "Field"; private string _customMessage = String.Empty; public int Min { get { return _min; } set { _min = value; } } public int Max { get { return _max; } set { _max = value; } } public string FieldName { get { return _fieldName; } set { _fieldName = value; } } public string CustomMessage { get { return _customMessage; } set { _customMessage = value; } } public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo) { int num = 0; if (!int.TryParse(value.ToString(), out num)) return new ValidationResult(false, String.Format("{0} must contain an integer value.", FieldName)); if (num < Min || num > Max) { if (!String.IsNullOrEmpty(CustomMessage)) return new ValidationResult(false, CustomMessage); return new ValidationResult(false, String.Format("{0} must be between {1} and {2}.", FieldName, Min, Max)); } return new ValidationResult(true, null); } } }
The magic all happens in the overridden Validate method. There are lots of additional ways you could customize this, but there should be enough in this example to get you started writing your own custom validation rules.
I put generalized rules like this in a separate project and namespace so I can reuse them in many projects. For rules that are specific to a particular DAL or Application you can just as easily put them in those namespaces.
Consuming Custom Validation Rules
Now that we have an IntegerValidationRule class, let’s add it to our XAML so we can put it into action. Unfortunately, there is no facility in Blend for doing so, so we’ll have to edit the XAML directly. The first thing to do is add a namespace reference to the XAML pointing to the location of the custom validation rules.
We are going to add the validation to a TextBox. Normally, I would use the shortened syntax to specify a binding, but in this case I’m going to use long hand because there is a lot of detail to add. Here is the completed sample:
<TextBox x:Name="Int1TextBox" Validation.ErrorTemplate="{StaticResource validationTemplate}" Style="{StaticResource textBoxInError}" VerticalAlignment="Center" HorizontalAlignment="Center" MinWidth="50" Margin="0,0,6,0"> <Binding Path="Int1" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay"> <Binding.ValidationRules> <ValidationRules:IntegerValidationRule Min="1" Max="9999999" FieldName="Int1" /> </Binding.ValidationRules> </Binding> </TextBox>
There are a number of things going on here. First, notice we added the Validation.ErrorTemplate property. This specifies a template to use when the error condition exists. I’ll cover that shortly along with the Style property. Both of these are used to customize the display of the error condition.
Next, in the TextBox.Text property we’ve added a long hand version of Binding. I use UpdateSourceTrigger to cue the validation check whenever the property changes. If I use the default value the change will only occur once the element loses focus, and I prefer a more real time effect.
Within the Binding property, we’ve added a subproperty section called Binding.ValidationRules. Inside this property we add references to the rules we wish to implement. This will create an instance of the custom validation rule with the specified property values. Since I set default values in the class, I can leave off any property and the validation will still function. As you can see in the example above, this is where I can set the property values for this particular instance of my IntegerValidationRule. In this case we are allowing the integer range to be between 1 and 9999999, and specifying the name we want the message to associate with any error.
That’s all we need to do to consume the custom rule. Now we’ll take a look at the ControlTemplate and Style to see how we can control the display.
Styling a Custom Validation
I took both the ControlTemplate and the Style from the MSDN article How To Implement Binding Validation. Here are the original values:
<ControlTemplate x:Key="validationTemplate"> <DockPanel> <TextBlock Foreground="Red" FontSize="20"><Run Text="!" /> </TextBlock> <AdornedElementPlaceholder /> </DockPanel> </ControlTemplate> <Style x:Key="textBoxInError" TargetType="{x:Type TextBox}"> <Style.Triggers> <Trigger Property="Validation.HasError" Value="true"> <Setter Property="ToolTip" Value="{Binding (Validation.Errors)[0].ErrorContent, RelativeSource={x:Static RelativeSource.Self}}" /> </Trigger> </Style.Triggers> </Style>
Insert these into the Windows.Resources section of the XAML. If you’ve been coding along, you can run the sample now and it will work. The only thing you really get is a red exclamation point to the left of the TextBox and a Tooltip if you hover over it. This is OK, but I didn’t really feel it was enough to grab the user’s attention, so I’m going to change it just a little.
Before I do that, though, take a look at the ControlTemplate: there is something interesting here called an AdornedElementPlaceholder. This nifty little element allows us to wrap the original ControlTemplate with additional elements. In this sample, we are placing a TextBlock with the exclamation point just in front of our original control. Feel free to experiment with this and add some code of your own around the offending TextBox.
Back to the Style: I want the TextBox to pop a little more when an error occurs. Looking at the original Style code, we see that we have a Trigger that changes the Style when Validation.HasError is True. In the default example, we are providing a ToolTip. Here I’ve added some code to make the Background color of the TextBox Red and the Foreground White whenever an error occurs:
<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}"> <Style.Triggers> <Trigger Property="Validation.HasError" Value="true"> <Setter Property="ToolTip" Value="{Binding (Validation.Errors)[0].ErrorContent, RelativeSource={x:Static RelativeSource.Self}}" /> <Setter Property="Background" Value="Red" /> <Setter Property="Foreground" Value="White" /> </Trigger> </Style.Triggers> </Style>
Now the user should definitely notice when an error occurs! Again, I recommend you experiment and see what else you can do to spice up your validation errors.
And another thing…
Another thing that would be very useful is to be able to bind to the state of validation. In other words, I have a button that I only want enabled if there are no Validation errors.
In WinForms, I used to process through all the Controls and check to see if any of them had a non-blank entry in the errorProvider. In WPF, what I would really like is to be able to bind IsEnabled to a “HasErrors” property somewhere. Unfortunately I couldn’t make something so simple work. I did, however, come up with a solution using a RoutedCommand. Commands and commanding are a topic for another time, but here is the short version of what I did.
I created a static RoutedCommand object in my Window class:
public static RoutedCommand ValidateEntryCommand = new RoutedCommand();
I then added the supporting CommandBinding to my XAML file, including CanExecute:
<Window.CommandBindings> <CommandBinding Command="{x:Static ValidationRulesPlay:Window1.ValidateEntryCommand}" Executed="CommandBinding_Executed" CanExecute="CommandBinding_CanExecute" /> </Window.CommandBindings>
In the CanExecute event handler, I call Validation.HasError(DependencyObject) for each TextBox I want to validate against and set the event args CanExecute property based on the results.
private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e) { bool hasError = Validation.GetHasError(Int1TextBox) || Validation.GetHasError(Int2TextBox); e.CanExecute = !hasError; }
Lastly, I set the Command property of the SaveButton to my Command name.
<Button x:Name="SaveButton" Content="Button" Command="{x:Static local:Window1.ValidateEntryCommand}"/>
Now, the button will be disabled if either of the TextBoxes have an error condition.
Conclusion
I hope this distills some of the basics for you and gets you on your way to writing your own custom validation rules. As always, comments are encouraged.
You can download the full solution for this project, which includes some supporting code.
One problem I found with ValidationRules in place of IDataErrorInfo is that passing a particular, non-static value to a ValidationRule via Binding is a pain in the butt. I switched to using IDataErrorInfo because by the time I get into the this[string] property to assemble the error message, the rest of my IDataErrorInfo-implementing instance is filled out with the dynamic data I need, so no Binding is necessary to pass it along.
Link to source code is dead, can you please provide?
Please read the last post on this blog from Sept 2012:
Hi, I learn a lot from your blog, but if there is any way to get the full solution for this sample? I can’t find it in http://joelcochran.com
Sorry, most of that code was lost long ago 😦