Databinding is a mechanism for moving the data from objects into UI controls. In this article, we’ll look at the databinding capabilities of Silverlight, including binding expressions, data templates, converters, binding modes, and validation. We’ll also go beyond the data binding mechanics to look to look at the patterns and practices you can use in a UI layer to build maintainable code.
Simple Data Binding
Databinding is the magic that that sits between your objects (know as a model) and the UI (known as a view). The model isn’t concerned with how the data will appear in front of the user – it’s job is to hold the data. The view, meanwhile, can take the model and render it inside of grids, charts, and lists.
There are only two concepts you need to know for basic data binding. First is the concept of a binding expression. A binding expression is used inside the XAML of a Silverlight component to describe the name of the property that you want fetched from the model. A binding expression lives inside of curly braces ({ and }), and you can see three examples in the source code below.
<UserControl x:Class="DataBindingDemo.SimpleBinding" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <StackPanel> <TextBlock Text="{Binding Name}" /> <TextBlock Text="{Binding Path=ID}" /> <TextBlock Text="{Binding Department.Name}"/> </StackPanel> </UserControl>
The first TextBlock in the XAML is using a binding expression on its Text property. The binding expression is telling Silverlight to pull the value for this Text property from the Name property of the model. The second TextBlock is fetching the ID property of the model using a slightly different (but equivalent) binding expression (the Path word is optional in simple expressions). You can even navigate an object hierarchy as demonstrated in the third TextBlock. In this case Silverlight will find a Department property on the model, then fetch the Name property from that object and use that for the Text property. All of the data binding options (such as the "Path”) are expressed as attributes inside the expression. We’ll see more options later in the article.
How does Silverlight know where the model comes from? That’s the second concept in data binding – the data context.
Most Silverlight controls will expose a DataContext property. By setting this property you are effectively telling Silverlight “here is the model to use”. Thus, the following C# code, combined with the above XAML, will produce the display shown to the right.
Employee employee = new Employee() { Name = "Scott", ID = 1, Department = new Department() { Name = "Engineering" } }; this.DataContext = employee;
You can set the DataContext at anytime as long as the underlying Silverlight component is initialized. In the above code, “this” represents the user control, so we are setting the data context for the entire control, and all the TextBlocks inside will fetch values from the same model object. This is because Silverlight travels “up” the tree of controls to find the DataContext where a model exists.
For example, the {Binding Name} in the first TextBlock will first look at the DataContext on the TextBlock itself. When that fails to find a model there it will look at the DataContext of the StackPanel that is a parent of the TextBlock. When that fails, Silverlight will find the DataContext at the UserControl level. This “context inheritance” means we only need to set the DataContext once for the entire page and it flows downwards, but if need be we could also set multiple DataContext properties inside the tree of controls, and even override inherited contexts.
Binding To Collections
Most data binding scenarios involve lists – such as a list of employees or a list of products. For these scenarios its common to use the Silverlight ListBox control. The ListBox knows how to display a collection of items by replicating a data template for each item. A data template describes the controls you want to use when displaying each individual item in a collection. You can almost think of a data template as a mini-control because it contains a bunch of child controls. The ListBox simply creates one instance of this mini-control for each object in the model’s collection. An example is shown below.
<ListBox ItemsSource="{Binding}"> <ListBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Name}"></TextBlock> </DataTemplate> </ListBox.ItemTemplate> </ListBox>
The ItemsSource property of the ListBox is the property that will reference the collection we want to databind. In this case we use the binding expression {Binding}, meaning we want the “whole” of the DataContext. In this scenario the model itself is a collection of objects. You could also drill into the model by with an expression such as {Binding Department.Employees}.
Notice the ItemTemplate of the list is set to a DataTemplate. Inside the DataTemplate is a single TextBlock to display the Name property of each object in the model. In this case we are using just a single TextBlock, but the powerful DataTemplate could include multiple controls, such as text blocks, images, videos, and colorful graphical elements. When the above XAML is combined with the code shown below, the result will be the image shown on the right.
var employees = new List<Employee>() { new Employee { Name = "Scott" }, new Employee { Name = "Poonam"}, new Employee { Name = "Paul" } }; this.DataContext = employees;
Quite often you’ll find data templates inside the resource section of a Page or application. The beauty of resources in Silverlight is that you can reuse resources across multiple pages in an application, and you also keep your XAML a little cleaner. Using such an approach with the above XAML would look like the following.
<UserControl.Resources> <DataTemplate x:Key="employeeTemplate"> <TextBlock Text="{Binding Name}"></TextBlock> </DataTemplate> </UserControl.Resources> <ListBox ItemsSource="{Binding}" ItemTemplate="{StaticResource employeeTemplate}"> </ListBox>
We can now reference the data template for displaying an employee by using the {StaticResource} binding and specifying a key of employeeTemplate. Although this template resource is only available inside of the same user control, we could take the template and place it in the resources section of the App.xaml file and reuse the template throughout the application.
Detecting Changes
Silverlight has the capability to update the UI when the underlying model changes. For example, if we are displaying the total number of hours worked by an employee, and we change an employee’s TotalHours property, the UI will automatically update to reflect the new total. For this to work we need our model objects to implement a magic interface.
For collections this magic interface is the INotifyCollectionChanged interface.
Fortunately, there is already a collection class we can use that implements INotifyCollectionChanged – this is ObservableCollection<T>. When you add or remove objects to an observable collection, the observable collection will automatically raise a CollectionChanged event. When Silverlight establishes a data binding it will look to see if the model collection implements the INotifyCollectionChanged interface and if so will automatically subscribe to the event. This means you don’t have any additional work to do when binding to an observable collection. If we bind an observable collection to the ItemsSource property of a ListBox, for example, then the ListBox will automatically display new objects we add to the collection, and will automatically remove objects we delete from the collection.
For non-collections the magic interface is the INotifyPropertyChanged interface. This interface demands that an object implement a PropertyChanged event. A typical implementation is shown below.
public class Employee : INotifyPropertyChanged { string _name; public string Name { get { return _name; } set { if (_name != value) { _name = value; RaisePropertyChanged("Name"); } } } int _departmentID; public int DepartmentID { get { return _departmentID; } set { if (_departmentID != value) { _departmentID = value; RaisePropertyChanged("DepartmentID"); } } } public event PropertyChangedEventHandler PropertyChanged; void RaisePropertyChanged(string propertyName) { var handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(propertyName)); } } }
All we need to do is raise the PropertyChanged event while passing along the name of the property that changed as an event argument. Silverlight will automatically subscribe to this event when it binds to an object that implements INotifyPropertyChanged. When we make changes to the underlying object the changes will be reflected immediately in the UI.
Beyond Simple Text
Binding works with all types of objects – not just simple primitive types like integers and strings. In the XAML below we are going to databind a property named Brush to the Fill property of a Rectangle.
<ListBox ItemsSource="{Binding}"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Code}" Width="90" /> <Rectangle Fill="{Binding Brush}" Width="100" Height="12" VerticalAlignment="Center" /> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox>
This XAML can bind to a collection of BrushDisplay objects we create in code. Each BrushDisplay has a Code property (of type string) that represents the hexadecimal RGB values for a color, and a SolidColorBrush property named Brush to paint the actual color. The screen shot on the right is an excerpt of what the code below will create.
byte[] v = { 0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF }; var list = new List<BrushDisplay>(); for (int r = 0; r < v.Length; r++) for(int g = 0; g < v.Length; g++) for(int b =0; b < v.Length; b++) list.Add( new BrushDisplay() { Code = "0x" + (v[r] + (v[g] << 8) + (v[b] << 16)) .ToString("X"), Brush = new SolidColorBrush( Color.FromArgb(255, v[r], v[g], v[b])) }); this.DataContext = list;
Of course not every data source may map perfectly to its destination. We might need to massage some data as it travels into the view, and vice versa (more on two-way data binding in a bit). In these scenarios we need a value converter. A value converter implements the IValueConverter interface. Values converters are powerful because they allow you to inject some logic into the binding process to perform conversions and formatting (a popular use of value converters is to format numbers and dates).
If you are displaying a DateTime, for example, the default conversion during databinding will create the display seen on the right. If we only want to display the date (and no time), we can use a value converter. Value converters are easy to create – you only need to implement Convert and ConvertBack methods. The class below is designed to convert dates to strings and back again.
public class DateFormatter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (parameter != null) { string formatString = parameter.ToString(); if (!string.IsNullOrEmpty(formatString)) { return string.Format(culture, formatString, value); } } return value.ToString(); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { if (value != null) { return DateTime.Parse(value.ToString()); } return value; } }
To use this new formatter we need to include it in our XAML. You can add formatters as resources inside of user controls, pages, and even at the application level. In the XAML below we are adding an instance of the formatter inside a user control’s resource dictionary with a key of “dateFormatter”. Notice we also include an XML namespace to CLR namespace mapping (xmlns:local="clr-namespace:DataBindingDemo") so that the XAML parser knows where to find the formatter.
<UserControl Width="400" x:Class="DataBindingDemo.EditTimeCard" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:DataBindingDemo" > <UserControl.Resources> <local:DateFormatter x:Key="dateFormatter" /> </UserControl.Resources>
We can then use the formatter in a data binding expression. The following XAML points to the dateFormatter key, so the formatter will be found in the application’s resources. Notice we can also pass along a ConverterParamter. In this case a “{0:d}” will tell the String.Format call to format the DateTime into a short date format (such as 1/1/2008 for a United States English culture setting).
<TextBox Margin="10" Text="{Binding Converter={StaticResource dateFormatter}, ConverterParameter='{0:d}', Path=PayPeriod.Start}"/>
Data Binding Modes
Silverlight features three data binding modes. The first mode, the default mode, is one-way data binding. In one-way data binding data moves from the model to the view only. Changes made in the view, such as inside of a TextBox control, will not change the underlying property in the model.
Two-way data binding allows changes to propagate back to the model. Two-way data binding is perfect for editable forms, but you do need to explicitly set the two-way mode. An example is shown below.
<TextBox Margin="10" Text="{Binding Mode=TwoWay, Path=TotalHours}"/> <TextBlock Text="Start Date"/> <TextBox Margin="10" Text="{Binding Mode=TwoWay, Converter={StaticResource dateFormatter}, ConverterParameter='{0:d}', Path=PayPeriod.Start}"/>
The mode is the OneTime mode. When you use OneTime binding, Silverlight will move data from the model to the view element only once. Even if the model uses INotifyPropertyChanged and the underlying data changes, the changes will not be reflected in the UI.
The DataGrid
Grids play a prominent role in many applications, and Silverlight includes a DataGrid control in the System.Windows.Controls.Data assembly. You can drag this control from the Visual Studio Toolbox window into your XAML file and Visual Studio will take care of referencing the correct assemblies and adding the proper XML namespace definitions to your XAML file. The DataGrid supports editing, sorting, and drag n’ drop columns. The DataGrid, like the ListBox, has an ItemsSource property we can use to set a binding expression. Simply setting up the binding after placing the grid in your XAML is enough to get started, as the grid is capable of auto-generating columns from the model (the grid will create a column for every public property).
<data:DataGrid AutoGenerateColumns="True" HeadersVisibility="All" ItemsSource="{Binding}" RowBackground="Cornsilk" AlternatingRowBackground="BlanchedAlmond" ColumnWidth="85" RowHeight="30" IsReadOnly="True" CanUserResizeColumns="False"> </data:DataGrid>
Simple customizations of the grid are possible just by setting some properties - background colors, grid lines, and resizable columns. For more control you can define the exact columns you want the grid to use by setting the Columns property. The Silverlight grid includes three types of columns: a GridTextColumn (to display text), a DataGridCheckBoxColumn (great for boolean properties), and a DataGridTemplate column. Like most templates in Silverlight, you can use a DataGridTemplate column to display nearly anything you want inside a grid – use calendars, stack panels, colored rectangles, etc. Notice the TimeCards column in the above screen shot. That grid is using auto-generated columns and doesn’t know how to display a property that is a collection. Now look at the following XAML:
<Grid x:Name="LayoutRoot" Background="White"> <data:DataGrid AutoGenerateColumns="False" ItemsSource="{Binding}" IsReadOnly="False"> <data:DataGrid.Columns> <data:DataGridTextColumn Binding="{Binding Name}" Width="50" /> <data:DataGridTemplateColumn> <data:DataGridTemplateColumn.CellTemplate> <DataTemplate> <data:DataGrid AutoGenerateColumns="True" ItemsSource="{Binding TimeCards}"/> </DataTemplate> </data:DataGridTemplateColumn.CellTemplate> </data:DataGridTemplateColumn> </data:DataGrid.Columns> </data:DataGrid> </Grid>
In the above XAML we’ll turn off auto-generated columns and explicitly define the columns for display. Using the DataGridTemplateColumn we can even define a nested data grid to display all the time cards for an employee. For more information about the grid control, I highly recommend the following blog posts.
- Scott Morrison – Using the Silverlight DataGrid, and Defining Columns for a Silverlight DataGrid.
- Manish Dalal: Silverlight Business Application: Part 0, Part 1, Part 2, Part 3, Part 4, Part 5, Part 6.
Also, be aware of a “bug” in the Silverlight 2.0 version of the DataGrid. Jesse Liberty describes the problem in his post “It Ain’t You, Babe … A Not-a-bug Bug in DataGrid”. The problem is that validation errors are tricky to catch with the DataGrid. Let’s look at validation next.
Exceptions and Validation
Validation is only a concern for TwoWay data bindings. When Silverlight moves data from your view back into your model – how do you control the values your model accepts? And how do you inform the user when they’ve entered illegal input?
First, let’s talk about the problems that can occur when data flows back into your model. The first potential problem is that Silverlight may not be able to convert the user’s input into the data type the model requires. This can happen when the user types “abc” into a field that expects integers, or types “123” into a field that requires a date. Some of these problems you try to avoid by using restricted controls (a calendar control for the user to enter a date, as an example), but this won’t avoid all conversion problems.
Another type of problem happens when Silverlight can convert the user’s input into the proper type, but your business logic determines the value of the data is illegal. One example is trying to place an order for –1 books. You want the quantity value of an order to always be positive. In these cases you must your model throw an exception when Silverlight sets the value. How do you know the exception occurred?
Silverlight can tell you about both types of validation errors, but you must set two properties on a binding to true. The first property is the ValidatesOnExceptions property. This property tells Silverlight to catch exceptions that occur when data moves from the view to the model. Again – this will be the exceptions thrown when trying to convert the user’s input, AND any exceptions thrown by the model itself.
In order to know about these exceptions you must also set the NotifyOnValidationError property to true. When true, Silverlight will raise a BindingValidationError event. This event will bubble up the visual tree of elements, so you can subscribe to the event on the element that is data bound, or at the element’s parent level, or the parent’s parent, and so on. In the following XAML we subscribe at the UserControl level.
<UserControl Width="400" x:Class="DataBindingDemo.EditTimeCard" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:DataBindingDemo" BindingValidationError="UserControl_BindingValidationError" > <UserControl.Resources> <local:DateFormatter x:Key="dateFormatter" /> </UserControl.Resources> <Grid x:Name="LayoutRoot" Background="White"> <StackPanel> <TextBlock Text="Total Hours"/> <TextBox Margin="10" Text="{Binding ValidatesOnExceptions=true, NotifyOnValidationError=true, Mode=TwoWay, Path=TotalHours}"/> <TextBlock Text="Start Date"/> <TextBox Margin="10" Text="{Binding Mode=TwoWay, Converter={StaticResource dateFormatter}, ConverterParameter='{0:d}', Path=PayPeriod.Start}"/> <TextBlock Text="End Date"/> <TextBox Margin="10" Text="{Binding Path=PayPeriod.End}"/> </StackPanel> </Grid> </UserControl>
The BindingValidationError event can tell you the source of an error, and also if the error is being added (the user just entered bad input) or removed (the user corrected a bad input). We can use this information to change elements on the screen, such as making the control that is the source of the failure turn red.
private void UserControl_BindingValidationError( object sender, ValidationErrorEventArgs e) { var control = e.OriginalSource as Control; switch (e.Action) { case ValidationErrorEventAction.Added: control.Background = new SolidColorBrush(_red); break; case ValidationErrorEventAction.Removed: control.Background = new SolidColorBrush(_white); break; } }
Conclusions
Silverlight data binding features are flexible and powerful, and provide you with everything you need to build an effective business application. Moving data back and forth has never been easier!