Wednesday, July 22, 2009

XamDataGrid Validation with NHibernate Validator

One of the good grid controls in WPF world belongs to Infragistics. With all the bells and whistles, there are still some limitation, some are WPF engine limitation and some are not. One thing that is very limiting is validation of data prior them being added to datasource. This is rather a broad subject, but I intended to use NHibernate entities (POCO objects) decorated with NHiberante validator attributes to do the validation for me.

NHibernate Validator

NHibernate Validator is a subproject in NHibernate Contrib which allows you validate plain business classes by using predefined set of validation attributes. The good thing is, as good as NHibernate uses POCO (Plain Old CLR Object) objects you can also validate any kind of simple classes using NHibernate validator. There are other validation frameworks out there but I didn’t wanted to clutter my object with the validation mechanism and I also didn’t want to write unnecessary properties on my domain objects (e.g. IsValid, etc.) so NHibernate Validator was a right choice for me. Let’s see how a POCO entity along with validation looks like:

public class Product
{
public Product()
{
}

public long ProductId
{
get;
private set;
}

[NotNullNotEmpty]
public string Title
{
get; set;
}

[NotNull]
public Category Category
{
get; set;
}

public string Description
{
get; set;
}

[Min(0)]
public long Quantity
{
get; set;
}
}

Binding To XamDataGrid

XamDataGrid supports binding to IEnumerable interface, so an IList<Product> would work. Also, XamDataGrid needs a public parameterless constructor if you’re going to add to that collection from the grid control. If this requirement is not met, grid control will silently ignore the NewLine setting value. Here’s the xaml snippet to display a grid with predefined fields (columns) and a NewLine row:

<igdp:XamDataGrid DataSource="{Binding ProductList}">
<
igdp:XamDataGrid.FieldLayouts>
<
igdp:FieldLayout>
<
igdp:FieldLayout.Settings>
<
igdp:FieldLayoutSettings AddNewRecordLocation="OnTopFixed" AllowDelete="False"
AllowAddNew="True" ExpansionIndicatorDisplayMode="Never"
/>
</
igdp:FieldLayout.Settings>
<
igdp:FieldLayout.Fields>
<
igdp:Field Name="Title" Label="Product Name" />
<
igdp:Field Name="Category" Label="Category" />
<
igdp:Field Name="Description" Label="Comment"/>
<
igdp:Field Name="Quantity" Label="Qtty" />
<
igdp:Field Name="ProductId" Visibility="Collapsed"/>
</
igdp:FieldLayout.Fields>
</
igdp:FieldLayout>
</
igdp:XamDataGrid.FieldLayouts>
</
igdp:XamDataGrid>
Now when you run the application and add a new Product object to the datasource, there’s no way to check if all the predefined validation rules are okay. So, how do we change the behavior of the grid control to:
  • Validate the product object PRIOR being added to the datasource so if an object is not validate, it won’t get added to the data source.

  • No redundant validation code should be written. We need to use NHibernate Validator infrastructure we already have in place.

  • A reusable mechanism. Our application might end up with lots of grid controls.

One way would be to subclass the grid control which although might work, is a heavy handed solution to our rather little problem. It is not right to put the code in our ViewModel (I’m using MVVM pattern), which introduces coupling betwee UI and ViewModel. Fortunately, this grid control exposes good events we can tap into and change the default validation behavior and we can do this by creating a new Attached Behavior.

Validation Attached Behavior

Attaching a behavior to an object, as the name puts it, means making the object do something that it would not do by default. According to Josh Smith:

“The idea is that you set an attached property on an element so that you can gain access to the element from the class that exposes the attached property. Once that class has access to the element, it can hook events on it and, in response to those events firing, make the element do things that it normally would not do. It is a very convenient alternative to creating and using subclasses, and is very XAML-friendly.”

…and that’s exactly what we’re going to do. We’re going to listen to one event in particular : “RecordUpdating” which fires when the user had edited an item and the control wants to submit the changes back to the data source. Here’s the part that does the validation:

private static void OnRecordUpdating(object sender, RoutedEventArgs e)
{
var arg = (RecordUpdatingEventArgs)e;
var dataobject = arg.Record.DataItem;
var rules = GetAllInvalidRules(dataobject);
var grid = (XamDataGrid)sender;

if (rules != null && rules.Count > 0)
{
var propertyName = rules[0].PropertyPath;
var propertyTitle = grid.DefaultFieldLayout.Fields[propertyName].Label;

arg.Action = RecordUpdatingAction.CancelUpdateRetainChanges;
arg.Record.Tag = string.Format("Check Field : {0}", propertyTitle);
}
else
{
arg.Record.Tag = null;
}

e.Handled = true;
}

Note : We only display one error at a time. If you need to display all of them at once, you can do so by changing the code a little bit.

We place the error information on the Record object’s Tag information. In our little RecordSelector style, we’ll display an error icon in case Tag property is set to some error information:
<Style TargetType="{x:Type igdp:RecordSelector}">
<
Setter Property="Template">
<
Setter.Value>
<
ControlTemplate TargetType="{x:Type igdp:RecordSelector}">
<
Image x:Name="ErrorIcon" Source="pack://application:,,,/GridValidation;component/Images/FieldError.png"
Width="16" Height="16" Margin="4,0,0,0" Visibility="Collapsed" ToolTip="{Binding Tag}" />
<
ControlTemplate.Triggers>
<
DataTrigger Binding="{Binding Path=Tag, Converter={StaticResource DefaultNullConverter}}" Value="False">
<
Setter TargetName="ErrorIcon" Property="Visibility" Value="Visible" />
</
DataTrigger>
</
ControlTemplate.Triggers>
</
ControlTemplate>
</
Setter.Value>
</
Setter>
</
Style>
One last thing that is remaining, is to connect our attached behavior to our xamDataGrid control. The great thing about the attached behaviors (one of them, at least) is that you can use them from XAML code directly and no code-behind is required at all. So back to our xamDataGrid code, here’s how to do the trick:
<igdp:XamDataGrid DataSource="{Binding ProductList}" bhv:DataGridValidationBehavior.HasRowValidation="true">

Conclusion

The final application looks like this picture. If you try to enter something wrong, either when creating a new item or editing one, validation mechanism will automagically kick in and does the work for you. The very important point is that data is retained in the new line but is it not added to the datasource, because the data is not validated.



xamGrid-Validation

You can download the source code from here. In order to fully compile and run the application, you need to install Infragistics WPF components which you can download here.


Submit this story to DotNetKicks Shout it

No comments: