Windows Presentation Foundation provides a simple way to validate user input on your data-bound controls, and it's completely customizable, too. By writing a custom class deriving from ValidationRule, and adding an error template to our control, we can have consistent UI notification of invalid input according to our specific needs.
A simple example would be a TextBox which requires a positive integer input. First, we write our custom validation rule.
public class posintValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
string _strInt = value.ToString();
int _int = -1;
if (!Int32.TryParse(_strInt, out _int))
return new ValidationResult(false, "Value must be an integer");
if (_int < 0)
return new ValidationResult(false, "Value must be positive");
return new ValidationResult(true, null);
}
}
Notice we return a ValidationResult with a custom error message if the validation fails. We'll use this message to give even more specific information about the error to the user. Next, we need a TextBox to test our validation rule. Assuming we will have multiple TextBoxes which should all display failed validation in the same way, we'll need to set our error template in the TextBox style.
<Style TargetType="{x:Type TextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border Background="{TemplateBinding Background}"
x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="5">
<ScrollViewer x:Name="PART_ContentHost"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Border BorderBrush="Yellow" BorderThickness="1" CornerRadius="5">
<AdornedElementPlaceholder></AdornedElementPlaceholder>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<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>
In our ErrorTemplate, we set a yellow border, and then inside, we put an AdornedElementPlaceHolder. This element represents the control itself, and is how the yellow border knows to position itself directly above the existing TextBox. I've also used a trigger on the Validation.HasError property to set the tooltip content to the ErrorContent of the ValidationResult. Now, all we need is a TextBox which uses our new validation rule. Unfortunately, there's no shortcut syntax for binding using validation rules that I can find, but if anyone gets one to work, I'd love to hear about it.
<TextBox Margin="10" Width="100">
<TextBox.Text>
<Binding Path="seconds" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:posintValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
I've set the binding path to a property called "seconds" of my object, and the UpdateSourceTrigger to PropertyChanged, so the user will be notified of a validation failure as soon as bad input is entered. Otherwise, the validation rule would execute when the TextBox lost focus. Here's what our control looks like in action. As long as the input is valid, the TextBox looks normal. But as soon as the user enters a non-integer or a negative value, the error template takes over.
This kind of immediate feedback makes a large form with lots of TextBoxes much easier for the user to fill in, and invalid values are not propagated to the object. As long as there is an error on a bound control, the value will not be sent back to the source. For more on WPF validation, here's a detailed article on MSDN.