Validation example with some more tricky databinding
Wednesday, September 24th, 2008In our WPF application we have come up with a control to give feedback to the user when validation errors occur. The best way to describe it is to show it in action. When an invalid value is entered the relevant control is highlighted in red and a button with a red info icon appears to the right:
Clicking on the button gives the user detailed information about the problem:
We’re pretty happy with how this validation control works in practice and it is quite easy to implement across the entire application.
We implement a ControlTemplate as an application level resource named StandardValidationTemplate as follows:
<ControlTemplate x:Key=“StandardValidationTemplate“> <Grid MaxWidth=“{Binding Path=AdornedElement.ActualWidth,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Adorner}}}” > <Grid.ColumnDefinitions > <ColumnDefinition Width=“*” /> <ColumnDefinition Width=“Auto” /> </Grid.ColumnDefinitions> <Border Grid.Column=“0” BorderBrush=“Red” BorderThickness=“1” > <AdornedElementPlaceholder /> </Border> <controls:ErrorButtonPopup Grid.Column=“1” Width=“24” Height=“24” ErrorMessage=“{Binding Path=AdornedElement.(Validation.Errors)[0].ErrorContent,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Adorner}}}” /> </Grid> </ControlTemplate>
This template is then applied to any controls that require validation by setting the Validation.ErrorTemplate attached property as follows:
<TextBox Validation.ErrorTemplate=“{StaticResource StandardValidationTemplate}” > <TextBox.Text> <Binding Path=“ZoomLevel“> <Binding.ValidationRules> <validate:IntegerRangeValidationRule MinValue=“13” MaxValue=“17” ErrorMessage=“Zoom level must be an integer between 13 and 17.” /> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox>
Here the IntegerRangeValidationRule is a simple ValidationRule written ourselves that checks that value is an integer within a certain range and ZoomLevel is the name of the property in the DataContext that we are binding to. When the validation fails the control is marked as invalid and the error message added to the collection of errors for that control. At this point the ControlTemplate for Validation.ErrorTemplate kicks in.
Our StandardValidationTemplate will wrap the problem control with a red Border and place it in a grid. The <AdornedElementPlaceholder /> element shows where the problem control (the one being adorned) is placed.
In the second column of the grid we will place an instance of our ErrorButtonPopup control. We need to set the ErrorMessage property of the ErrorButtonPopup. This is the message that is displayed to the user when the button is clicked. Setting this involves some typically cryptic WPF databinding syntax, but demonstrating the power of WPF databinding it can be done in a single statement.
ErrorMessage=“{Binding Path=AdornedElement.(Validation.Errors)[0].ErrorContent, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Adorner}}}“
Breaking this down into its various parts, first we need to get a reference to the control being validated. To do that we get a reference to the Adorner control that our ErrorButtonPopup is contained within. That is got using the RelativeSource syntax:
RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Adorner}}
This is not too difficult, the RelativeSource markup extension finds the first Ancestor of type Adorner. The Adorner then has an AdornedElement property which in this case is the TextBox we were looking for.
Now we just need to get at the error message. Controls have a collection of validation errors for cases where more than one validation is attached to it. Our control only displays the first failing validation, it could be modified to handle more if necessary. The Validation.Errors attached property gives us a collection of ValidationError objects. We are just interested in the ErrorContent property of the first one.
Path=AdornedElement.(Validation.Errors)[0].ErrorContent
It’s a little tricky to figure out all this syntax but once in place everything works perfectly. The best thing about this is that now adding validation to any control is one line:
Validation.ErrorTemplate=“{StaticResource StandardValidationTemplate}“
And modifying the look of the validation can be all done in one place.