Screen Validation With Data Annotations in WPF

In the article Using Data Annotations to validate models I showed that it is possible to keep validations in attributes.

In this article I will show how to apply these validations on the client side.

Note: All your models must implement INotifyPropertyChanged, and to make it simple and clean, I will be using the Fody PropertyChanged Nuget package.

In order to tell the screen it should validate the bound property we need to implement the IDataErrorInfo interface. For example:

  1. [PropertyChanged.ImplementPropertyChanged]    
  2. public abstract class PropertyValidateModel : IDataErrorInfo    
  3. {    
  4.     // check for general model error    
  5.     public string Error { get { return null; } }    
  6.      
  7.     // check for property errors    
  8.     public string this[string columnName]    
  9.     {    
  10.         get    
  11.         {    
  12.             var validationResults = new List<ValidationResult>();    
  13.      
  14.             if (Validator.TryValidateProperty(    
  15.                     GetType().GetProperty(columnName).GetValue(this)    
  16.                     , new ValidationContext(this)    
  17.                       {    
  18.                           MemberName = columnName    
  19.                       }    
  20.                     , validationResults))    
  21.                 return null;    
  22.      
  23.             return validationResults.First().ErrorMessage;    
  24.         }    
  25.     }    
  26. }    
This validation method is similar to the validation in the other article, however it validates a property instead of the entire model. With this class I can now inherit from it and my model will automatically be implementing IDataErrorInfo.
  1. [PropertyChanged.ImplementPropertyChanged]    
  2. public class Game : PropertyValidateModel    
  3. {    
  4.     [Required]    
  5.     [StringLength(5)]    
  6.     public string Name { getset; }    
  7.      
  8.     [Required]    
  9.     [StringLength(5)]    
  10.     public string Genre { getset; }    
  11.      
  12.     [Required]    
  13.     [Range(13, 40)]    
  14.     public int MinAge { getset; }    
  15. }   
On my view I need to bind the properties with some additional settings as in the following:
  1. <TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged    
  2.     , NotifyOnValidationError=True, ValidatesOnDataErrors=True}" />  

 

  • UpdateSourceTrigger will tell the view to notify changes as they happen.
  • NotifyOnValidationError will notify when there are errors.
  • ValidatesOnDataErrors will enable validation.

The outcome will be:

outcome

However by simply doing it this way we do not get any error messages displayed, so we do not know what is wrong with the data. To display the errors we need to do a little trick with the Validation.ErrorTemplate.

The code following binds, using a trigger, the TextBox's ToolTip to the first error encountered in the control. And by setting the TextBox's error template we can display the error message by accessing the AdornedElement and grabbing the ToolTip where the error message is contained. If you do not want to use the ToolTip you can use the Tagproperty instead.

  1. <Style TargetType="TextBox">    
  2.     <Setter Property="Validation.ErrorTemplate">    
  3.         <Setter.Value>    
  4.             <ControlTemplate>    
  5.                 <StackPanel>    
  6.                     <Border BorderThickness="2" BorderBrush="DarkRed">    
  7.                         <StackPanel>    
  8.                             <AdornedElementPlaceholder    
  9.                                 x:Name="errorControl" />    
  10.                         </StackPanel>    
  11.                     </Border>    
  12.                     <TextBlock Text="{Binding AdornedElement.ToolTip    
  13.                         , ElementName=errorControl}" Foreground="Red" />    
  14.                 </StackPanel>    
  15.             </ControlTemplate>    
  16.         </Setter.Value>    
  17.     </Setter>    
  18.     <Style.Triggers>    
  19.         <Trigger Property="Validation.HasError" Value="true">    
  20.             <Setter Property="BorderBrush" Value="Red" />    
  21.             <Setter Property="BorderThickness" Value="1" />    
  22.             <Setter Property="ToolTip"    
  23.                 Value="{Binding RelativeSource={RelativeSource Self}    
  24.                     , Path=(Validation.Errors)[0].ErrorContent}" />    
  25.         </Trigger>    
  26.     </Style.Triggers>    
  27. </Style>   
Running the application again gets:

Running the application