Sunday, January 30, 2011

DataGrid Validation using IDataErrorInfo in MVVM - WPF

In this post I will demo how you can apply validation rules on WPF's DataGrid using IDataErrorInfo interface. This example uses popular MVVM pattern but if you are not using MVVM you can still grasp the main idea about using IDataErrorInfo for validation in WPF. I will create a DataGrid in my application which will have two columns Name and Age. I will apply two validation rules on my DataGrid. 1) Name cannot be empty. 2) Age must be greater than 20. I will notify user about the validation error using tooltip displaying corresponding error and a red border around the cell which contains invalid value. You can download complete ready to run demo from here.

I have defined a class Person which consist of two fields Name and Age. I will bind item source of my DataGrid with collection of Person class to display list of persons in my DataGrid.

   1:      /// <summary>
   2:      /// Derived from Base, Implements IDataErrorInfo
   3:      /// </summary>
   4:      public class Person : Base, IDataErrorInfo
   5:      {
   6:          string m_Name;
   7:          int m_Age;
   8:   
   9:          public Person()
  10:          {
  11:              m_Name = string.Empty;
  12:              m_Age = 0;
  13:          }
  14:   
  15:          public string Name
  16:          {
  17:              get
  18:              {
  19:                  return m_Name;
  20:              }
  21:              
  22:              set
  23:              {
  24:                  if(value != m_Name)
  25:                  {
  26:                      m_Name = value;
  27:                      //notify the binding that my value has been changed
  28:                      OnPropertyChanged("Name");
  29:                  }
  30:              }
  31:          }
  32:   
  33:          public int Age
  34:          {
  35:              get
  36:              {
  37:                  return m_Age;
  38:              }
  39:   
  40:              set
  41:              {
  42:                  if (value != m_Age)
  43:                  {
  44:                      m_Age = value;
  45:                      //notify the binding that my value has been changed
  46:                      OnPropertyChanged("Age");
  47:                  }
  48:              }
  49:          }
  50:   
  51:          public string Error
  52:          {
  53:              get { throw new NotImplementedException(); }
  54:          }
  55:   
  56:          //IDataErrorInfo Property
  57:          public string this[string columnName]
  58:          {
  59:              get 
  60:              { 
  61:                  //this get will be invoked everytime user change the Name or Age filed in Datagird
  62:                  //columnName contains the property name which is modified by user.
  63:                  string error = string.Empty;
  64:                  switch(columnName)
  65:                  {
  66:                      case "Name":
  67:                          //if user changes the name field, I check if the new value is empty or not
  68:                          //if it is empty I set the error message accordingly.
  69:                          if(string.IsNullOrEmpty(m_Name))
  70:                              error = "Name cannot be empty";
  71:                      break;
  72:   
  73:                      case "Age":
  74:                          //if user change the Age, I verify that Age is greater than 20,
  75:                          //if not I set the error message.
  76:                          if(m_Age < 20)
  77:                              error = "Age must be greater than 20";
  78:                      break;
  79:                  }
  80:                  //just return the error or empty string if there is no error
  81:                  return error;
  82:              }
  83:           
  84:          }
  85:      }
Note that Person class has been derived from Base which is implementing INotifyPropertyChanged interface to tell the binding instantly that a proprty.

I am using the Person class created above in MainWindowData which is the main class whose object is set as DataContext of MainWindow i.e. our view. This is done in MainWindow.xaml.cs. MainWindowData has a PersonCollection which is an ObservableCollection of  Person class and this collection will be set as an item source of DataGrid.

I have defined application's view/UI in XAML. It consists of a style for TextBlock and a DataGrid consisting of two template columns, first for Name second for Age. Inside window's resources, I have defined a style for TextBlock type. In this style I am defining that whenever Validation Error occurs, display the error message in a tooltip. I will be using this style in my CellTemplate for both the columns of my DataGrid.

   1:  <Style x:Key="TextBlockStyle" TargetType="{x:Type TextBlock}">
   2:       <Style.Triggers>
   3:            <Trigger Property="Validation.HasError" Value="true">
   4:                 <Setter Property="ToolTip"
   5:                 Value="{Binding RelativeSource={RelativeSource Self},
   6:                 Path=(Validation.Errors)[0].ErrorContent}"/>
   7:            </Trigger>
   8:       </Style.Triggers>
   9:  </Style>
I have binded ItemSource of my DataGrid with PersonCollection. DataGrid consists of two template columns Name and Age. I have set TextBlock as CellTemplate and TextBox as CellEditingTemplate for both the columns. I have applied the style on CellTemplate which is defined in window resources to display the error in tooltip.

   1:  <DataGrid ItemsSource="{Binding PersonCollection}" AlternatingRowBackground="Wheat" AutoGenerateColumns="False">
   2:              <DataGrid.Columns>
   3:                  <DataGridTemplateColumn  Header="Name" MinWidth="150">
   4:                      <DataGridTemplateColumn.CellTemplate>
   5:                          <DataTemplate>
   6:                              <TextBlock Style="{StaticResource ResourceKey=TextBlockStyle}" Text="{Binding Path=Name,  UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnValidationError=True}"/>
   7:                          </DataTemplate>
   8:                      </DataGridTemplateColumn.CellTemplate>
   9:   
  10:                      <DataGridTemplateColumn.CellEditingTemplate>
  11:                          <DataTemplate>
  12:                              <TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"/>
  13:                          </DataTemplate>
  14:                      </DataGridTemplateColumn.CellEditingTemplate>
  15:                  </DataGridTemplateColumn>
  16:   
  17:                  <DataGridTemplateColumn Header="Age" MinWidth="150">
  18:                      <DataGridTemplateColumn.CellTemplate>
  19:                          <DataTemplate>
  20:                              <TextBlock Style="{StaticResource ResourceKey=TextBlockStyle}" Text="{Binding Age, UpdateSourceTrigger=PropertyChanged, NotifyOnValidationError=True, ValidatesOnDataErrors=True}"/>
  21:                          </DataTemplate>
  22:                 </DataGridTemplateColumn.CellTemplate>
  23:   
  24:                 <DataGridTemplateColumn.CellEditingTemplate>
  25:                      <DataTemplate>
  26:                           <TextBox Text="{Binding Age, UpdateSourceTrigger=PropertyChanged}"/>
  27:                      </DataTemplate>
  28:                 </DataGridTemplateColumn.CellEditingTemplate>
  29:            </DataGridTemplateColumn>
  30:       </DataGrid.Columns>  
  31:  </DataGrid>
These were the main parts of complete example. If user enters an empty string in the Name column, he/she will get error like this

You can download the complete ready to run sample from here and play with it.



3 comments:

  1. I cannot download the example. Can you post it again?
    Thanks.

    ReplyDelete
  2. The file cannot be downloaded.

    ReplyDelete
  3. The file is not available for download!

    ReplyDelete