Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

Validation in WPF Toolkit’s DataGrid

5.00/5 (5 votes)
5 Nov 2011CPOL2 min read 61.2K   3.2K  
Describe validation when presenting data in WPF Toolkit’s DataGrid.

screenshot.jpg

Introduction

This article presents a way to validate data, which is shown in the CodePlex WPF Toolkit’s DataGrid control (http://wpf.codeplex.com/). The validation is done using the IDataError interface and works in .NET Framework 3.5 and above.

Background

Recently, I had to validate data in a grid control. I didn’t want to buy a third party component, but decided to use the WPF Toolkit’s DataGrid.

After reading the excellent article from Colin Eberhardt (http://www.codeproject.com/KB/WPF/WPFDataGridExamples.aspx), I realized some problems when using .NET Framework 3.5 especially when data is not changed by the user but added programmatically.

I had to implement a small tool which could be deployed through extracting a Zip file. Because of this, I didn’t want the user to need to install .NET Framework 4.0 only to solve my validation problems.

In the end, I did the data validation as described below which works with .NET Framework 3.5.

Using the Code

The application uses the MVVM pattern. If you haven’t heard about it, I recommend you read the good articles from Josh Smith (http://joshsmithonwpf.wordpress.com/).

The class where the validation takes place is called <PersonVM>. This class contains the following properties:

  • String FirstName
  • String LastName
  • Boolean HasJob (Indicates if the person has a job or is out-of-work)
  • String JobName

Two things should be validated:

  1. The first name and last name should only contain A-Za-z chars and spaces.
  2. It should not be allowed for a person to have the HasJob flag set, but the JobName empty, or vice versa.

The first rule can be checked by validating each name property. To check the second rule, it is not only sufficient to validate a single property, but multiple properties. This validation takes place on the whole Person object.

The validation is implemented in the following PersonVM indexer (desired by the <IDataError> interface):

C#
public string this[string columnName]
{
  get
  {
    // apply property level validation rules
    if (columnName == "FirstName")
    {
      if (String.IsNullOrEmpty(this.FirstName))
        return "First Name needs to be filled";
        
      if (!MyNameEx.Match(this.FirstName).Success)
        return "First Name may only contain characters or spaces";
    }
    
    if (columnName == "LastName")
    {
      if (String.IsNullOrEmpty(this.LastName))
        return "Last Name needs to be filled";
        
      if (!MyNameEx.Match(this.LastName).Success)
        return "Last Name may only contain characters or spaces";
    }
    
    // Method ValidateJob applies object level validation. In this example the consistency 
    // of the properties HasJob and JobName is checked on object level. An error is 
    // announced for JobName only. Otherwise the red error border would be presented for 
    // the JobName and the HasJob field and the error correction would be inconvenient 
    // for the user - try it out by uncommenting following line
    // if (columnName == "JobName" || columnName == "HasJob")
    if (columnName == "JobName")
    {
      return ValidateJob();
    }
    
    return "";
  }
}

private string ValidateJob()
{
  if (!this.HasJob && !String.IsNullOrEmpty(this.JobName))
  {
    return "Job Name is given, but Job Flag is not set!";
  }
  if (this.HasJob && String.IsNullOrEmpty(this.JobName))
  {
    return "Job Name is not given, but Job Flag is set!";
  }
  return "";
}

The second method of the IDataError is not written individually for PersonVM and can be put into a base or helper class.

C#
public string Error
{
  get
  {
    StringBuilder error = new StringBuilder();
 
    // iterate over all of the properties
    // of this object - aggregating any validation errors
    PropertyDescriptorCollection props = TypeDescriptor.GetProperties(this);
    foreach (PropertyDescriptor prop in props)
    {
      String propertyError = this[prop.Name];
      if (propertyError != string.Empty)
      {
        error.Append((error.Length != 0 ? ", " : "") + propertyError);
      }
    }
 
    return error.Length == 0 ? null : error.ToString();
  }
}

The validation of multiple fields needs to be announced by one field - JobName in my example - because the user cannot edit multiple fields in one step to correct a multiple field error. For this reason, it is necessary to simulate that JobName has been changed when the HasJob checkbox is updated. This notification is done in the HasJob setter:

C#
public Boolean HasJob
{
  get
  {
    return myHasJob;
  }
  set
  {
    myHasJob = value;
    NotifyPropertyChanged("JobName");
    NotifyErrorChanged();
  }
}

The corresponding XAML code of the DataGrid is displayed below:

XML
<Window x:Class="ValidationInWpfDatagrid.View.MainWindow" 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:dg="http://schemas.microsoft.com/wpf/2008/toolkit"
        Title="MainWindow" Height="350" Width="525">
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition/>
      <RowDefinition Height="20"/>
    </Grid.RowDefinitions>
    <dg:DataGrid  Name="myDataGrid" AutoGenerateColumns="False" 
    ItemsSource="{Binding PersonList}" CanUserAddRows="True">
 
      <dg:DataGrid.Resources>
        <Style TargetType="{x:Type dg:DataGridCell}">
          <Setter Property="TextBlock.ToolTip" 
                    Value="{Binding Error}" />
        </Style>
      </dg:DataGrid.Resources>
 
      <dg:DataGrid.Columns>
        <dg:DataGridTextColumn Header="FirstName" 
           Binding="{Binding Path=FirstName, ValidatesOnDataErrors=True}" 
           Width="*" />
        <dg:DataGridTextColumn Header="LastName" 
           Binding="{Binding Path=LastName, ValidatesOnDataErrors=True}" 
           Width="*" />
        <dg:DataGridCheckBoxColumn Header="Job Flag" 
           Binding="{Binding Path=HasJob, ValidatesOnDataErrors=True}" 
           Width="*" />
        <dg:DataGridTextColumn Header="Job's Name" 
           Binding="{Binding Path=JobName, ValidatesOnDataErrors=True}" 
           Width="*" />
      </dg:DataGrid.Columns>
 
    </dg:DataGrid>
    <Button Grid.Row="1" Name="myBtnAddPerson" 
       Content="Add Person" Command="{Binding AddPersonCommand}" />
  </Grid>
</Window> 

Have fun!

History

  • 23 Oct 2011: First revision.
  • 5 Nov 2011: Corrected object level validation.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)