Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / WPF

Validation in WPF Toolkit’s DataGrid

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
25 Oct 2011CPOL2 min read 59.5K   3.2K   22  
Describe validation when presenting data in WPF Toolkit’s DataGrid
This is an old version of the currently published article.
Download ValidationInWpfDatagrid.zip - 216.86 KB 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 up from .NET Framework 3.5.

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.

At the end, I came up to do the data validation as described below which works with .NET Framework 3.5.

Using the Code

The application uses the MVVM pattern. If you didn’t hear about, I recommend you to 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 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";
    }
    
    // apply object level validation rules this way
    if (columnName == "HasJob" || 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
    HashSet<String> errorMessages = new HashSet<String>();
    PropertyDescriptorCollection props = TypeDescriptor.GetProperties(this);
    foreach (PropertyDescriptor prop in props)
    {
      String propertyError = this[prop.Name];
      if (errorMessages.Contains(propertyError))
        continue;
      else
        errorMessages.Add(propertyError);
      if (propertyError != string.Empty)
      {
        error.Append((error.Length != 0 ? ", " : "") + propertyError);
      }
    }
 
    return error.Length == 0 ? null : error.ToString();
  }
}

The errorMessages hashset is used to eliminate duplicate error messages as the occur when object level validation takes place.

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

  • First revision

License

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


Written By
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

Discussions on this specific version of this article. Add your comments on how to improve this article here. These comments will not be visible on the final published version of this article.