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:
- The first name and last name should only contain A-Za-z chars and spaces.
- 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):
public string this[string columnName]
{
get
{
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";
}
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.
public string Error
{
get
{
StringBuilder error = new StringBuilder();
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:
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
- 23 Oct 2011: First revision.
- 5 Nov 2011: Corrected object level validation.