Click here to Skip to main content
Click here to Skip to main content

Validation in WPF Toolkit’s DataGrid

, 25 Oct 2011 CPOL
Rate this:
Please Sign up or sign in to vote.
Describe validation when presenting data in WPF Toolkit’s DataGrid
This is an old version of the currently published article.
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):

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.

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:

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

The corresponding XAML code of the DataGrid is displayed below:

<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)

Share

About the Author

Frank Augustin

Germany Germany
No Biography provided

Comments and Discussions


Discussions posted for the Published version of this article. Posting a message here will take you to the publicly available article in order to continue your conversation in public.
 
GeneralMy Vote 5 PinmemberShemeemsha RA2-Oct-14 19:36 
QuestionHow can we do this for a DataGridTemplateColumn? PinmemberXiaoAnn19-Dec-12 6:49 
Questionvery nice PinmemberCIDev7-Nov-11 9:20 
QuestionWhen in error, the text field is not editable. PinmemberMember 65991223-Oct-11 6:14 
AnswerRe: When in error, the text field is not editable. PinmemberFrank Augustin23-Oct-11 20:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.141015.1 | Last Updated 25 Oct 2011
Article Copyright 2011 by Frank Augustin
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid