In a previous article, how does C# code behind interact with an XAML interface within a Silverlight 2 context? - INotifyCollectionChanged: Part V we demonstrate how to implement INotifyCollectionChanged interface in order to notify XAML UI when collection object source is changed. In this article, we will see how data validations are leveraged. This article is supposed to respond the question posed in the article how does C# code behind interact with an XAML interface within a Silverlight 2 context? - Introduction: Part I, which is: what if user sets properties in inappropriate format, how to handle this situation and avoid the application from the trouble and failure?
First, we should leverage code so that notifications will be sent to the user. Once the error occurs, the binding engine sends notifications to the targeted XAML UI, and then tells it that data aren't in the appropriate format. Let's consider this example, say a form that will be used by a given user at the front end to enter data about him/her and then a given process will store all data into a data base. A question could be asked at this level. What if a funny user tries to make some dismissive things? Something un- expectable like this figure bellow shows :
Figure 1
Something wrong with this above data, hence it will be a catastrophe if data about ages are negatives or worst if they are represented in none numeric format, at the other hand, the gender could be one of two values either male or female but not a Big foot .
Ok, how to deal and prevent this from happening before it is happening? We can implement validation logic to avoid this.
First, let's add the Person class at the C# code behind level. This class will tie our XAML user interface with the C# code behind world.
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Age { get; set; }
public string Gender { get; set; }
}
I defined all types of the class as strings for two reasons. The first one is to simplify the process and the second one is that data conversion will constitute the theme of the next article how does C# code behind interact with an XAML interface within a Silverlight 2 context? -Data Conversion-Part VII.
Now, let's return to our Person class that should be rectified to fit the validation operation needs. First, let's derive four new exception types. One for none numeric entries, and another one for numeric entries, and then another one for the age that must be numeric and should respect a certain range, and then finally one latest exception for the gender that should only be either "Male" or "Female" and nothing else, not a Bigfoot by the way.
Here are the four exceptions' definitions:
public class NoneNumericValueException : Exception
{
public NoneNumericValueException()
: base("This value should be numeric")
{ }
}
public class NumericValueException : Exception
{
public NumericValueException()
: base("This value should be none numeric")
{ }
}
public class AgeException : Exception
{
public AgeException()
: base("The age should be not less than 0 and not more than 150")
{ }
}
public class GenderException : Exception
{
public GenderException()
: base("This value should be either ‘Male’ or ‘Female’")
{ }
}
And then, we will make use of the regular expressions to throw the exceptions from within the class Person that should be defined as follow:
using System.ComponentModel;
using System.Text.RegularExpressions;
public class Person : INotifyPropertyChanged
{
public Person() { }
private string _FirstName;
public string FirstName
{
get { return _FirstName; }
set
{
if (!Regex.IsMatch(value, @"^[a-zA-Z ]+$"))
throw new NumericValueException();
_FirstName = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("FirstName"));
}
}
}
private string _LastName;
public string LastName
{
get { return _LastName; }
set
{
if (!Regex.IsMatch(value, @"^[a-zA-Z ]+$"))
throw new NumericValueException();
_LastName = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("LastName"));
}
}
}
private string _Age;
public string Age
{
get { return _Age; }
set
{
int intValue = Convert.ToInt32(value);
if (!Regex.IsMatch(value, @"^\d{*}?") || intValue < 10 || intValue > 150)
throw new AgeException();
_Age = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Age"));
}
}
}
private string _Gender;
public string Gender
{
get { return _Gender; }
set
{
if (!Regex.IsMatch(value, "Male") && !Regex.IsMatch(value, "Female"))
throw new GenderException();
_Gender = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Gender"));
}
}
}
public override string ToString()
{
return string.Format("First name: {0}, last name: {1},age: {2} ,geder: {3}",
FirstName,
| LastName,
Age,
Gender);
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
The System.ComponentModel and System.Text.RegularExpressions namespaces should be added because the first one enables us to make use of the INotifyPropertyChanged for eventual changes that could affect one of the four Person class properties. The second namespace helps us to build regular expressions and compare them with the entered data. If a given data is valid then nothing will happen. Else the specific exception according to the given case will be thrown.
Remark: For more details about the regular expressions and how to deal with them, I advise to take a look on this Paul's article.
Let's switch now to the XAML side and redesign our interface in order to fit the validation operations:
<UserControl x:Class="Silverlight.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:code="clr-namespace:Silverlight"
Width="250" Height="350">
<StackPanel x:Name="LayoutRoot"
Margin="10"
Orientation="Vertical"
Background="Navy"
>
<StackPanel.Resources>
<code:Person x:Name="myPerson"></code:Person>
</StackPanel.Resources>
<TextBlock Text="First name"
FontSize="14"
Margin="5,5,5,5"
FontWeight="Bold"
Foreground="White"/>
<TextBox x:Name="txtFirstName"
Margin="5"
Text="{Binding Source={StaticResource myPerson},Mode=TwoWay,Path=FirstName,NotifyOnValidationError= true, ValidatesOnExceptions= true}"
BindingValidationError="txtFirstName_BindingValidationError"
/>
<TextBlock Text="Last name"
FontSize="14"
Margin="5,5,5,5"
FontWeight="Bold"
Foreground="White"/>
<TextBox x:Name="txtLastName"
Margin="5"
Text="{Binding Source={StaticResource myPerson},Mode=TwoWay,Path=LastName, NotifyOnValidationError= true, ValidatesOnExceptions= true }"
BindingValidationError="txtLastName_BindingValidationError"
/>
<TextBlock Text="Age"
FontSize="14"
Margin="5,5,5,5"
FontWeight="Bold"
Foreground="White"/>
<TextBox x:Name="txtAge"
Margin="5"
Text="{Binding Source={StaticResource myPerson},Mode=TwoWay,Path=Age, NotifyOnValidationError= true, ValidatesOnExceptions= true }"
BindingValidationError="txtAge_BindingValidationError"
/>
<TextBlock Text="Gender"
FontSize="14"
Margin="5,5,5,5"
FontWeight="Bold"
Foreground="White"/>
<TextBox x:Name="txtGender"
Margin="5"
Text="{Binding Source={StaticResource myPerson},Mode=TwoWay,Path=Gender, NotifyOnValidationError= true, ValidatesOnExceptions= true }"
BindingValidationError="txtGender_BindingValidationError"
/>
<Button Content="Validate"
Margin="0,20"
Width="100"
Height="20"
Click="Button_Click"
></Button>
</StackPanel>
</UserControl>
The XAML UI will look as under:
Figure 2
It is very important to set NotifyOnValidationError and ValidatesOnExceptions to true, because the first property, once is set to true, it tells the binding engine to create a validation error when an exception occurs, meanwhile, the second property will tells the binding engine to raise the BindingValidationError event. This even will occur exactly when a data validation error is reported from the source object, the Person class instance in this case.
But the question is how the given user will be notified if a specified format is not respected?
For that, we have two choices. The first one is to implement the BindingValidationError event handler of the container, the Stack Panel in our case.
Figure 3
private void StackPanel_BindingValidationError(object sender, ValidationErrorEventArgs e){}
if (e.Action == ValidationErrorEventAction.Added){
//TO DO: Leverage code to notify user that something is wrong
}
else if (e.Action == ValidationErrorEventAction.Removed){
//TO DO: Leverage code to notify user that all thing are come back to the normal
}
}
The second choice which is not advised but we will leverage it for demonstration purposes is in fact that each one of the text boxes will have an implemented BindingValidationError event handler as under:
First, let's get the text boxes instances within the C# code behind:
TextBox txtF;
TextBox txtL;
TextBox txtA;
TextBox txtG;
public Page()
{
InitializeComponent();
//The first name field
txtF = this.FindName("txtFirstName") as TextBox;
//The last name field
txtL = this.FindName("txtLastName") as TextBox;
//The age field
txtA = this.FindName("txtAge") as TextBox;
//The gender field
txtG = this.FindName("txtGender") as TextBox;
}
Then we do implement each one of those text boxes BindingValidationError event handlers as under:
private void txtFirstName_BindingValidationError(object sender, ValidationErrorEventArgs e)
{
if (e.Action == ValidationErrorEventAction.Added){
txtF.Background = new SolidColorBrush(Colors.Red);
txtF.Foreground = new SolidColorBrush(Colors.White);
}
else if (e.Action == ValidationErrorEventAction.Removed){
txtF.Background = new SolidColorBrush(Colors.White);
txtF.Foreground = new SolidColorBrush(Colors.Black);
}
}
private void txtLastName_BindingValidationError(object sender, ValidationErrorEventArgs e)
{
if (e.Action == ValidationErrorEventAction.Added){
txtL.Background = new SolidColorBrush(Colors.Red);
txtL.Foreground = new SolidColorBrush(Colors.White);
}
else if (e.Action == ValidationErrorEventAction.Removed){
txtL.Background = new SolidColorBrush(Colors.White);
txtL.Foreground = new SolidColorBrush(Colors.Black);
}
}
private void txtAge_BindingValidationError(object sender, ValidationErrorEventArgs e)
{
if (e.Action == ValidationErrorEventAction.Added){
txtA.Background = new SolidColorBrush(Colors.Red);
txtA.Foreground = new SolidColorBrush(Colors.White);
}
else if (e.Action == ValidationErrorEventAction.Removed){
txtA.Background = new SolidColorBrush(Colors.White);
txtA.Foreground = new SolidColorBrush(Colors.Black);
}
}
private void txtGender_BindingValidationError(object sender, ValidationErrorEventArgs e)
{
if (e.Action == ValidationErrorEventAction.Added){
txtG.Background = new SolidColorBrush(Colors.Red);
txtG.Foreground = new SolidColorBrush(Colors.White);
}
else if (e.Action == ValidationErrorEventAction.Removed){
txtG.Background = new SolidColorBrush(Colors.White);
txtG.Foreground = new SolidColorBrush(Colors.Black);
}
}
The final step is to run the application and observe what's happening, say that we put a big foot as a gender entry, the result will be:
Figure 4
That's it
Good Dotneting!!!