The new release of Silverlight has silently introduced some new improvements to the DataBinding that
bring the RIA platform closer to WPF for some aspects, and at the same time strongly simplify the development
of data driven applications. In this article we will explore deeply these new features.
Download Source Code
Since the first release of WPF, the DataBinding was one of the most important and appreciated features of this platform. This is due to its powerful capabilities and its simplicity that let the developer create complex interfaces with a few lines of codebehind moving many thing to declarative programming in XAML. DataBinding emerged in Silverlight since the release 2.0 but it had a number of limitations if compared to the WPF counterpart.
From its first appearance the DataBinding has been continuously improved in Silverlight with the intent of making it closer to how it works in Windows Presentation Foundation. In Silverlight 3.0 it gained the important element-to-element binding allowing developers to connect elements each other in the page. Now in Silverlight 4.0 the databinding has been again improved with some new features that make it easier to use and effective.
Binding To DependencyObject
The first and to me most important improvement of DataBinding in Silverlight 4.0 involve an architectural aspect that was a great limitation in previous versions and a deep difference between Silverlight and WPF. If you ever tryied to connect a slider with a PlaneProjection or with a Transformation you may have noticed that the binding is possible only from the slider to the other element and not vice versa.
The reason for this behavior is hidden in the structure of DataBinding of the old Silverlight versions where the root of DataBinding was the FrameworkElement class. Since Projection and Transform inherits from UIElement that is a subclass of DependencyObject they are unable to support binding. The Slider control instead derive from Control that is a FrameworkElement before being a DependencyObject so it is allowed to be target of databinding. So this two snippets of code are not equivalent in Silverlight as they are in WPF:
<Rectangle Fill="Red" Width="100" Height="100">
<Rectangle.RenderTransform>
<RotateTransform x:Name="transform" Angle="{Binding ElementName=slider, Path=Value}" />
</Rectangle.RenderTransform>
</Rectangle>
<Slider x:Name="slider" Minimum="0" Maximum="360" />
...
<Rectangle Fill="Red" Width="100" Height="100">
<Rectangle.RenderTransform>
<RotateTransform x:Name="transform" />
</Rectangle.RenderTransform>
</Rectangle>
<Slider x:Name="slider" Minimum="0" Maximum="360" Value="{Binding Path=Angle, ElementName=transform, Mode=TwoWay}" />
At first sight it may seem that there is not any need to have a specular behavior in this feature because if one of the two directions works correctly we have solved the binding. Unfortunately the drawbacks of this limitation are multiple. First of all you have to use an unnatural Binding - that specify the Mode=TwoWay - because the source of the data is binded to the consumer, while usually there is no need to use TwoWay when the consumer connects to the data source in the right direction.
But the major trouble is that you are unable to connect multiple DependecyObjects to the same DataSource. If you think at the Slider example of some rows above you may notice that it is normal to want connect a lot of consumers (e.g. some Trasform) to a single source (a Slider) to be able to command all the consumers with a single knob.
In Silverlight 4.0 this limitation has been overwhelmed because the Silverlight team have reestablished the specularity of the DataBinding bringing the root of the DataBinding to the DependencyObject class. In the code I wrote for this article you have an example of what I'm saying. With two sliders you can act on the angle of rotation of a plane projection, but at the same time a gauge and a textbox are updated by the same control. The result is that you do not need to write any code in the codebehind of the page.
New properties for binding
Another improvement that Silverlight 4.0 introduce on the DataBinding - taking it directly from Windows Presentation Foundation - is the availability of a bunch of new properties on the BindingBase class from where the Binding markup extension is born. These new properties let simplify the usage of the markup extension avoiding the adoption of multiple converters. The new properties are:
1. StringFormat - This property is used to format the input of a Binding expression before it is assigned to the target property. The StringFormat accepts the same formatting options of the Format method of the string class, and enable to easily manipulate dates,numeric values and so on.
// format a double with a fixed number of decimals
{Binding Path=Angle,StringFormat=##0.0}
// format a date
{Binding Path=Birthday,StringFormat='dd MMM yyyy'}
// format a number in a string
{Binding Path=Age,StringFormat='I am {0} years old'}
2. TargetNullValue - This property let you specify a value to be show when the source property is null. It is useful to prevent empty values in controls that do not support null
// show a placeholder value for null
{Binding Path=Name,TargetNullValue='please enter a name'}
3. FallBackValue - This property let you specify a fallback value to show when the binding is no able to determine the value of the target property. This may happen, as an example, when the target property does not exists due to a mismatch binding.
// show an error when a property does not exists
{Binding Path=InvalidProperty,FallbackValue='property does not exists'}
IDataErrorInfo and INotifyDataErrorInfo
The Validation support in Silverlight 3.0 come from the availability of ValidatesOnExceptions and NotifyOnValidationError. These two properties enable the developer to raise an exception when a value is invalid on a property. The binded control will catch the exception and show the related error message. This kind of validation, accompained by the use of the Validation attributes is really powerful and simple to use but with Silverlight 4.0 another kind of validation has been added.
It comes in two flavours. An old-style IDataErrorInfo interface - for sure already known by Windows Forms programmers e WPF - and a really new INotifyDataErrorInfo interface that is Silverlight only. While the purpose of these interfaces is the same they act in a very different way:
IDataErrorInfo is an interface that exists in the .NET Framework since the version 1.0. It was originally added to the Windows Forms platform to support the validation of DataSets. When WPF was shipped the interface has been used to implement validation also in this new technology.
Now the interface becomes part of Silverlight but its purpose is to have compatibility for legacy classes already in place or used in both WPF and Silverlight. Infact the interface is limited and does not solve completely the validation task. To implement the interface you have to write two read-only properties:
public interface IDataErrorInfo
{
string Error { get; }
string this[string columnName] { get; }
}
All the work is done by the indexer property. It is called by the runtime every time the user change a property on the interface and returns a string representing the error detected in the value of the property. If an error is not detected the property must return null. The Error property is not used by the validation and may return always null. Here is an example showing how to implement the indexer:
public string this[string columnName]
{
get
{BindingExpression
if (columnName == "FirstName")
{
if (string.IsNullOrEmpty(this.FirstName))
return "First Name is required";
}
else if (columnName == "LastName")
{
if (string.IsNullOrEmpty(this.LastName))
return "Last Name is required";
}
else if (columnName == "FiscalCode")
{
if (string.IsNullOrEmpty(this.FiscalCode))
return "Fiscal Code is required";
if (!Regex.IsMatch(this.FiscalCode, "^[a-zA-Z]{6,6}[0-9]{2,2}[a-zA-Z][0-9]{2,2}[a-zA-Z][0-9]{3,3}[a-zA-Z]$"))
return "Invalid Fiscal specified";
}
else if (columnName == "Email")
{
if (string.IsNullOrEmpty(this.Email))
return "Email is required";
if (!Regex.IsMatch(this.Email, @"^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|""(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*"")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$"))
return "Invalid Email specified";
}
return null;
}
}
To start monitoring this interface you have to enable a property on the Binding expression. It is the ValidatesOnDataErrors property. When the property has been set to true the runtime will starts call the interface to get the errors.
<TextBox Text="{Binding Path=FirstName,ValidatesOnDataErrors=True,Mode=TwoWay}"
Margin="3" />
Since IDataErrorInfo is slightly obsolete the better you can do is using the new INotifyDataError interface that was created to support asyncronous binding validation. As the name suggest the interface is event driven and notify the runtime whenever it detect one or more errors on the target object.
public interface INotifyDataErrorInfo
{
bool HasErrors { get; }
event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
IEnumerable GetErrors(string propertyName);
}
Implementing this interface is not really easy and straightforward. The developer have to check the properties and when a change is notified he must raise the ErrorsChanged event. Then the runtime checks if the HasErrors property is set to true. It indicate that some errors has been detected so they can be read using the GetErrors method. in the following box I show you a minimal implementation of the interface:
public class PersonAsyncViewModel : INotifyDataErrorInfo
{
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public bool HasErrors
{
get
{
return (from e in this.Errors
where e.Value.Count > 0
select e).Count() > 0;
}
}
public System.Collections.IEnumerable GetErrors(string propertyName)
{
return this.GetErrorsByProperty(propertyName);
}
private Dictionary<string, List<string>> Errors { get; set; }
public PersonAsyncViewModel()
{
this.Errors = new Dictionary<string, List<string>>();
}
private string firstName;
public string FirstName
{
get { return firstName; }
set
{
if (this.firstName != value)
{
this.firstName = value;
this.UpdateValidationErrors("FirstName");
}
}
}
private void UpdateValidationErrors(string propertyName)
{
List<string> errors = this.GetErrorsByProperty(propertyName);
errors.Clear();
if (propertyName == "FirstName")
{
if (string.IsNullOrEmpty(this.FirstName))
errors.Add("First Name is required");
}
this.OnErrorsChanged(propertyName);
}
private List<string> GetErrorsByProperty(string propertyName)
{
if (!this.Errors.ContainsKey(propertyName))
this.Errors.Add(propertyName, new List<string>());
return this.Errors[propertyName];
}
protected virtual void OnErrorsChanged(string propertyName)
{
EventHandler<DataErrorsChangedEventArgs> handler = this.ErrorsChanged;
if (handler != null)
handler(this, new DataErrorsChangedEventArgs(propertyName));
}
}
As the IDataErrorInfo interface to enable INotifyDataErrorInfo you have to set a boolean property on the Binding markup extension. It is the ValidatesOnNotifyDataErrors.
Notifying error changes in an asyncronous way is a powerful tool when you are working with the Model-View-ViewModel pattern because it allow to handle easily long running validations like that require a roundtrip to the server. As an example the can be uniqueness validation and check for referential constraints. In the example I provided you can find a property that use a Ria Service to check that a Fiscal Code is not duplicated on the database.
The close... the better
As you have saw the changes to the databinding in Silverlight 4.0 are all targeted to make thin the difference between WPF and Silverlight. The sole exception to this rule is the INotifyDataError interface, created only for Silverlight to give a better implementation of the old IDataErrorInfo. The purpose of making Silverlight and WPF more closer than ever is an important task - perhaps second only to the cross platform availability of the plugin - because it allow the reuse of code and the porting of WPF applications to the web with Silverlight. I think the Silverlight team is aware of it and is working fine to accomplish the task.