This is part three of Building Business Application with Silverlight series that showcases the basic building blocks of a data centric application.
Series Link: Part 0, Part 1, Part 2
Almost all business application will have varying degree of validations. For our example, let say we have a business rule that states that Age must be a number and be greater than 0 and less than 200.
DataGrid already prevents non numeric entry in Age column, thanks to backing field of type integer. Behind the scene, DataGrid uses System.Convert by way of DataGridValueConverter to coerce values to proper type.
Try to change age to a non number, you will see that as soon as you exit cell, it reverts back to old values. This is because DataGrid tries to convert string to number and when it fails (FormatException), it reverts data back to original value. (If you examine Output window of Visual Studio Editor you will find the message: A first chance exception of type 'System.FormatException' occurred in mscorlib.dll)
You can see this by turning on two attributes on the binding, ValidatesOnExceptions and NotifyOnValidationError. Setting ValidatesOnExceptions to true tells the binding engine to create a validation error when an exception occurs and setting NotifyOnValidationError to true tells the binding engine to raise the BindingValidationError event when a validation error occurs.
Change the DataGrid declaration from
<data:DataGrid x:Name="peopleDataGrid" Grid.Row="1" />
to
<data:DataGrid
x:Name="peopleDataGrid"
AutoGenerateColumns="False"
Grid.Row="1"
>
<data:DataGrid.Columns>
<data:DataGridTextColumn
Header="First Name"
DisplayMemberBinding="{Binding FirstName}"
/>
<data:DataGridTextColumn
Header="Last Name"
DisplayMemberBinding="{Binding LastName}"
/>
<data:DataGridTemplateColumn
Header="Age"
>
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Age}"
/>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
<data:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox
Text="{Binding Path=Age,Mode=TwoWay,NotifyOnValidationError=true,ValidatesOnExceptions=true}"
/>
</DataTemplate>
</data:DataGridTemplateColumn.CellEditingTemplate>
</data:DataGridTemplateColumn>
<data:DataGridTextColumn
Header="City"
DisplayMemberBinding="{Binding City}"
/>
</data:DataGrid.Columns>
</data:DataGrid>
Now setup event handler for BindingValidationError on the peopleDatagrid in the page.xaml.cs file as
public Page() {
InitializeComponent();
this.Loaded += new RoutedEventHandler(Page_Loaded);
//this.addButton.Click += new RoutedEventHandler(addButton_Click);
this.peopleDataGrid.KeyDown += new KeyEventHandler(peopleDataGrid_KeyDown);
this.deleteButton.Click += new RoutedEventHandler(deleteButton_Click);
this.peopleDataGrid.BindingValidationError += new EventHandler<ValidationErrorEventArgs>(peopleDataGrid_BindingValidationError);
}
void peopleDataGrid_BindingValidationError(object sender, ValidationErrorEventArgs e) {
System.Diagnostics.Debug.WriteLine(e.Error.Exception.Message);
}
F5 and run the application. Enter non numeric value in Age field.
If you examine the “Output” window, you will see that we are getting exception - Input string was not in a correct format..
We can use above event handler to provide user with more descriptive error message as well as visual feedback. Add following code to BindingValidationError event handler
void peopleDataGrid_BindingValidationError(object sender, ValidationErrorEventArgs e) {
System.Diagnostics.Debug.WriteLine(e.Error.Exception.Message);
if (e.Action == ValidationErrorEventAction.Added) {
((Control)e.Source).Background = new SolidColorBrush(Colors.Red);
this.Dispatcher.BeginInvoke(() => HtmlPage.Window.Alert(e.Error.Exception.Message));
} else if (e.Action == ValidationErrorEventAction.Removed) {
((Control)e.Source).Background = new SolidColorBrush(Colors.Red);
}
}
Now when you try to enter string, our code gets the notification, user is presented with a alter box and color of cell changes to red to indicate error.
You can use Exception type and Source to differentiate between different columns and customize the message. Change BindingValidationError event handler as shown
void peopleDataGrid_BindingValidationError(object sender, ValidationErrorEventArgs e) {
System.Diagnostics.Debug.WriteLine(e.Error.Exception.Message);
if (e.Action == ValidationErrorEventAction.Added) {
((Control)e.Source).Background = new SolidColorBrush(Colors.Red);
string message = e.Error.Exception.Message;
if (e.Error.Exception is System.FormatException
&& "Age" == ((Control)e.Source).Tag.ToString()) {
message = "Age must be a number between 0 and 200";
}
this.Dispatcher.BeginInvoke(() => HtmlPage.Window.Alert(message));
} else if (e.Action == ValidationErrorEventAction.Removed) {
((Control)e.Source).Background = new SolidColorBrush(Colors.White);
}
}
Also change xaml CellEditingTemplate for Age as follows
<data:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox
Text="{Binding Path=Age,Mode=TwoWay,NotifyOnValidationError=true,ValidatesOnExceptions=true}"
Tag="Age"
/>
</DataTemplate>
</data:DataGridTemplateColumn.CellEditingTemplate>
Notice addition of Tag to identify Age column. If you now try to enter non number in Age field, you get custom error message
Let consider domain validation for age. It should not be negative and between 0 and 200. Lets add that validation. Most logical place is setter of Age property in Person class. Change Age setter as
public int Age {
get { return _age; }
set {
if (value == _age) return;
if (value < 0 || value > 200) {
throw new Exception("Age must be between 0 and 200");
}
_age = value;
OnPropertyChanged("Age");
}
}
Here we are checking to make sure that age is not negative and less than 200. If not, we throw exception.
One alternative to disruptive alert message box is to use tooltip. Change BindingValidationError event handler as shown
private void peopleDataGrid_BindingValidationError(object sender, ValidationErrorEventArgs e) {
if (e.Action == ValidationErrorEventAction.Added) {
((Control)e.Source).Background = new SolidColorBrush(Colors.Red);
((Control)e.Source).SetValue(ToolTipService.ToolTipProperty, e.Error.Exception.Message);
} else if (e.Action == ValidationErrorEventAction.Removed) {
((Control)e.Source).Background = new SolidColorBrush(Colors.White);
((Control)e.Source).SetValue(ToolTipService.ToolTipProperty, null);
}
}
Also change xaml CellEditingTemplate for Age as follows
<data:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox
Text="{Binding Path=Age,Mode=TwoWay,NotifyOnValidationError=true,ValidatesOnExceptions=true}"
ToolTipService.ToolTip="Please provide Age between 0 and 200"
/>
</DataTemplate>
</data:DataGridTemplateColumn.CellEditingTemplate>
Notice addition of ToolTipService.ToolTip to Age column. This provides user with nice help when Age goes in edit mode
And when business rules fails
It will be nice to prevent user form entering string or negative values in first place. You can do that using AttachedProperties to extend control behavior (similar to AjaxControlToolkit FilterTextboxExtender), but that’s topic for another post!
Above takes care of all sync validations that provide user with immediate feedback. However, what about async validation? We will tackle that next!