This article is compatible with the latest version of Silverlight.
In the previous part of this article I introduced a custom Form control I made for some real world projects. I've briefly explained the reasons why I choose to not use the Silverlight Toolkit's DataForm, just before to show how to build this control, and I've also detailed what I like of the DataForm and what I should retain in my Form control. One of this features is the validation of the input and it is what I'm about to describe in the second part.
Download Source Code
The databinding in Silverlight directly supports the validation through a couple of attributes, that let the developer specify if the markup extension has to notify validation exceptions to the control binded to a property so it can show the errors to the user. This kind of validation is based on the check of the value assigned to a property then the user change the value. Just to make an example, the validation of a TextBox is triggered by the writing of some charachters into it. This cause the binded property to be changed and then an eventual validation exception to be raised.
There is one major drawback in this tecnique that takes place if a validation is required when the value of a property has not been changed. The common example is the RequiredAttribute attribute. If you apply this attribute to a property you will expect to notify the user when he does not assign any value to the input control. But no check of the value is done if the user does not change it, so when he tries to submit the input it is considered valid even if it is empty.
Unfortunately the RequiredAttribute is one of most common checks in the input validation, at least common as the type check. The tecnique to solve this problem require to manually trigger the update of the binding source and is very verbose in the page code behind.
The use of the DataForm control solve the problem, because the control is aware of it and takes a reference to any binding expression used in the form and just before submitting the input raise the validation events using the UpdateSource() method. This is the sole available tecnique because it is able to raise the validation exceptions, also if caused by the attributes, and let them to reach the control that has to display the error.
Adding Validation to the Form
After understanding the troubles caused by validation and how the toolkit's DataForm behave, it is time to add validation support to the custom Form control. Basically I need to add the same behavior of the DataForm just because this leave me free to forget the problem when I use the control. What I have to do is:
1) Scan the Form content to detect BindingExpression instances
2) Enumerate the BindingExpressions and call the UpdateSource method to let each Binding updates the related property
3) Check if validation errors has occured and eventually stop the flow before raising the Commit event.
The first task of this checklist is the most hard to understand. You are to be aware that the developer simply adds some markup as content of the Form and every element of this markup may contains a Binding Expression connection a property to the DataContext. So the problem is that we have to scan each element of the content and each DependencyProperty of the every single element. To scan the elements I can use the same extension method I've used in the previous part to detect the Commands but I have to introduce another method that is able to enumerate the DependencyProperties:
public static IEnumerable<DependencyProperty> DependencyProperties<T>(this T element)
where T : DependencyObject
{
List<DependencyProperty> list = new List<DependencyProperty>();
if (element != null)
{
if (element is Panel ||
element is Button ||
element is Image ||
element is ScrollViewer ||
element is TextBlock ||
element is Border ||
element is Shape ||
element is ContentPresenter ||
element is RangeBase)
return list;
FieldInfo[] fields = element
.GetType()
.GetFields(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Static);
if (fields == null)
return list;
foreach (FieldInfo info in fields)
if (info.FieldType == typeof(DependencyProperty))
list.Add((DependencyProperty)info.GetValue(null));
}
return list;
}
What I do in this snippet is to use reflection to detect the static fields of the element which are of type DependencyProperty. You probably know that to define a DependencyProperty you have to add a static field used by the framework to contain informations about the property itself. So when I found a field of this type I'm sure to have found a DependencyProperty. I can now use write the code to extract the bindings:
private List<BindingInfo> DetectBindings()
{
List<BindingInfo> bindingExpressions = new List<BindingInfo>();
FrameworkElement content = this.ContentPresenterElement.Content as FrameworkElement;
if (content != null)
{
foreach (FrameworkElement element in content.Elements<FrameworkElement>())
{
bindingExpressions.AddRange(from be in
from dp in element.DependencyProperties()
select new BindingInfo(element.GetBindingExpression(dp), dp, element)
where be.BindingExpression != null && Object.Equals(this.DataContext, be.BindingExpression.DataItem)
select be);
}
}
return bindingExpressions;
}
Starting from the ContentPresenter I enumerate the elements and for each one I extract the DependencyProperties which have a BindingExpression. The DependencyProperty class has a method named GetBindingExpression that is able to return the instance of the associated BindingExpression.
The DetectBindings method returns a list of the bindings found in the content. For performance purpose I skip some type of elements I'm sure do not have an useful binding. So now I can accomplish the second task of the list and call the UpdateSource method.
/// <summary>
/// Updates all sources.
/// </summary>
private void UpdateAllSources()
{
List<BindingInfo> bindings = this.DetectBindings();
foreach (BindingInfo bi in bindings)
bi.BindingExpression.UpdateSource();
}
The result of this flow is the updating of the values, from the controls to the properties, and obviously the throwing of the exceptions if the values violate one of the rules applied. The user will see the fields become red and the ValidationSummary will show a list of the errors. This flow is started every time an user hit one of the controls connected to the commit command. To avoid the raise of the commit command when an error is found I use a property of the ValidationSummary. The HasDisplayedErrors is true when the summary detect an error.
How to use the control
The control I described until now is available in the attached code. I figure out some of you will want to have a try, so now I will show you how to use the control. As I said at the beginning of the article, the control is pretty much like an HTML form tag where the content is submitted to the server to a page defined by the action attribute. My form let the developer specify bindings in markup, like you always do with a DataTemplate, and raise a commit or cancel event you can attach from the codebehind in traditional event-based programming, or with a Prism command in MVVM scenario. Here is a snippet:
<controls:Form cmd:Commit.Command="{Binding Parent.AddCommand}"
DataContext="{Binding Person}"
Header="Add People Form"
HeaderStyle="{StaticResource FormHeaderStyle}"
Padding="5"
ValidationSummaryVisibility="{Binding Path=IsChecked,ElementName=chkSummary,Converter={StaticResource BoolToVisibility}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.5*"/>
<ColumnDefinition Width="0.5*"/>
</Grid.ColumnDefinitions>
<dft:DataField LabelPosition="Top">
<TextBox Text="{Binding FirstName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" />
</dft:DataField>
<dft:DataField LabelPosition="Top" Grid.Column="1">
<TextBox Text="{Binding LastName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" />
</dft:DataField>
<dft:DataField LabelPosition="Top" Grid.ColumnSpan="2" Grid.Row="1">
<swc:DatePicker SelectedDate="{Binding Birthday, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" />
</dft:DataField>
<dft:DataField LabelPosition="Top" Grid.ColumnSpan="2" Grid.Row="2">
<TextBox Text="{Binding Email, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" />
</dft:DataField>
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal" VerticalAlignment="Bottom" Grid.Row="3" Margin="0,5,5,5" Grid.ColumnSpan="2">
<CheckBox x:Name="chkSummary" Foreground="Black" Margin="0,0,15,0" VerticalAlignment="Center" Content="Enable validation summary"/>
<Button controls:Form.TriggerCommand="Commit" controls:Form.TriggerEvent="Click" Content="Add Person" Width="80" />
</StackPanel>
</Grid>
</controls:Form>
As you may have noticed the layout of the Form is a Grid, and I use some DataFields inside of it to define the fields and its labels. The last row of the grid contains a StackPanel where I've put the submit button and a CheckBox to show how to hide the ValidationSummary. The good thing is that you can bind the CheckBox value directly to the ValidationSummaryVisibility property and write less code, thanks to Element-to-Element binding. The Header property lets me specify the content of the header. I've used a text but it is binded to a ContentControl so you can use more complex XAML code.
Finally I've to say two words about the ViewModel. You can see, the Form is connected to a Person property, but this is not an instance of a Person business object, but a child ViewModel where I specified Validation and Display attributes. The major drawback of binding directly on the DataContext property come from the fact that the Commit command have to be binded to the child ViewModel itself also if it have to be used in the parent. So I simply create the child ViewModel with a Parent property to expose the parent ViewModel and its commands. Here is the code:
public class PersonViewModel<T> : ObservableObject
{
public T Parent { get; set; }
[Display(Name = "First Name")]
[Required]
[StringLength(10)]
public string FirstName
{
get { return this.GetValue<string>("FirstName"); }
set { this.SetValue<string>("FirstName", value); }
}
[Display(Name = "Last Name")]
public string LastName
{
get { return this.GetValue<string>("LastName"); }
set { this.SetValue<string>("LastName", value); }
}
[Display(Name = "Birthday")]
public DateTime Birthday
{
get { return this.GetValue<DateTime>("Birthday"); }
set { this.SetValue<DateTime>("Birthday", value); }
}
[Display(Name = "Email Address")]
[RegularExpression(@"^((?>[a-zA-Z\d!#$%&'*+\-/=?^_`{|}~]+\x20*|""((?=[\x01-\x7f])[^""\\]|\\[\x01-\x7f])*""\x20*)*(?<angle><))?((?!\.)(?>\.?[a-zA-Z\d!#$%&'*+\-/=?^_`{|}~]+)+|""((?=[\x01-\x7f])[^""\\]|\\[\x01-\x7f])*"")@(((?!-)[a-zA-Z\d\-]+(?<!-)\.)+[a-zA-Z]{2,}|\[(((?(?<!\[)\.)(25[0-5]|2[0-4]\d|[01]?\d?\d)){4}|[a-zA-Z\d\-]*[a-zA-Z\d]:((?=[\x01-\x7f])[^\\\[\]]|\\[\x01-\x7f])+)\])(?(angle)>)$",
ErrorMessage="Invalid Email Format")]
public string Email
{
get { return this.GetValue<string>("Email"); }
set { this.SetValue<string>("Email", value); }
}
public PersonViewModel(T parent)
{
this.Parent = parent;
this.Birthday = DateTime.Today;
}
}
The GetValue and SetValue members come from the base class that implements the INotifyPropertyChanged interface using a Dictionary. As in a generated business object when I set a value I expect to get validation exceptions. Obviously you can do this by hand implementing the interface directly on the ViewModel but I prefer using a custom ObservableObject base class. The SetValue method checks that the value is valid:
protected virtual void SetValue<T>(string name, T value)
{
if (this.Properties.ContainsKey(name))
{
if (!object.Equals(this.Properties[name], value))
{
this.ValidateProperty(name, value);
this.Properties[name] = value;
this.OnPropertyChanged(name);
}
}
else
{
this.ValidateProperty(name, value);
this.Properties.Add(name, value);
this.OnPropertyChanged(name);
}
}
protected virtual void ValidateProperty(string name, object value)
{
ValidationContext ctx = new ValidationContext(this, null, null);
ctx.MemberName = name;
Validator.ValidateProperty(value, ctx);
}
Conclusion
I've used the control in an MVVM scenario to accomplish two tasks: the first one is the creation of input forms related to the editing of the rows of a table (something you usually do with the regular DataForm) , but I found it useful also to validate the input of an user that is applying a filter to a search. Enjoy the control in the attached code and feel free to contact me if you have any question.