Data binding provides a way of connecting your user interface to a source of data, such as an object in your application. This is an elegant means of displaying data in your user interface and enabling the user to update the data and have those changes automatically pushed back into the bound object.
Rather than pushing data into the user interface from the code-behind, XAML's powerful data binding capabilities enable the user interface to pull data in from the appropriate source and populate itself as required.
Silverlight supports a very rich and flexible data binding model, and this data binding model is one of Silverlight's key strengths and one you will find extremely useful when building business applications. In Silverlight, you can bind to objects, resources, and even properties on other controls. As long as a property is a dependency property (dependency properties are a special type of property, discussed in Chapter 12), you can bind it to almost anything. Data binding is implemented using the Binding
markup extension.
A data binding requires a source and a target. The source is usually a property on an object, and the target is usually a property on a control. When bound together, the property on the control will be assigned the value of the property on the object.
Figure 2-15 provides a visual interpretation of a binding, connecting the FirstName
property of a class named Person
(source) to the Text
property of a TextBox
control (target).
There are a number of ways to set the source of a data binding. Let's look at those now.
As previously mentioned, you bind a property of a control to a property on an object in XAML using the Binding
markup extension. Recall from earlier in this chapter that a markup extension is just a class containing some logic, which is evaluated at runtime and returns a value as a result. When used in XAML, markup extensions are denoted by the curly brackets surrounding them. Before we get into how we specify what source that a binding should use, let's actually set up that source.
Say we have a class called Person
in our project, containing the properties FirstName
and LastName
, matching the fields in our user interface created earlier:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName
{
get { return FirstName + " " + LastName; }
}
}
This class has to be instantiated as an object, and its properties should be assigned some values before it is consumed by a binding. We could push a Person
object into the user interface via some code-behind or use a data source control to obtain it, but for this example, we'll take a XAML-only approach, and instantiate the object as a resource and bind to that. To do so, we must first add a namespace declaration to our XAML file to reference the CLR namespace in which the Person
class is defined and assign a prefix that we can use to reference the namespace by (shown in bold):
<UserControl x:Class="Chapter02Workshop.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:my="clr-namespace:Chapter02Workshop"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400" >
Then, we can instantiate the object as a resource in the resources of the UserControl:
<UserControl.Resources>
<my:Person x:Key="personObject" />
</UserControl.Resources>
Note You can assign the object initial values if you wish when instantiating the object, in the same manner as you would assign property values on any control in XAML:
<UserControl.Resources>
<my:Person x:Key="personObject" FirstName="Chris" LastName="Anderson" />
</UserControl.Resources>
Now, we have an object that our bindings can use as a data source. Let's say we have a TextBox control and want to bind its Text
property (the target) to the FirstName
property on this object (the source). There are two things that we need to set as part of this binding: the object we should use as the data source and the property on that object it should get the value from. The Binding
markup extension has a Source
property and a Path
property, so we can point these properties to the object resource and property name respectively, like so:
<TextBox Text="{Binding Source={StaticResource personObject}, Path=FirstName}" />
One problem with this syntax is that you often have many bindings on a control that use the same data source, and specifying the data source on every binding leads to messy and unmaintainable XAML. The solution to this is to assign the data source to the control's DataContext
property. You can omit setting the Source
property on the bindings, and the bindings on this control will then use the value assigned to the DataContext
property as their data source, unless a binding specifies otherwise.
<TextBox DataContext="{StaticResource personObject}" Text="{Binding Path=FirstName}" />
However, you often have many controls whose bindings share the same data source, and assigning the same data source to the DataContext
property on each control still leads to messy and unmaintainable XAML. Fortunately, when you assign a value to the DataContext
property on a control, the value will also be inherited down the object hierarchy. This means that any controls below that control will also have the object assigned to their DataContext
properties. Let's say all the controls that you wished to bind to the object were in a Grid control. You could, therefore, simply assign the object to the Grid's DataContext
property, and all controls within it would inherit its value for their own data context, without requiring you to assign their DataContext
properties individually:
<Grid DataContext="{StaticResource personObject}">
<TextBox Grid.Column="1" Grid.Row="0" Margin="3" Text="{Binding Path=FirstName}" />
<TextBox Grid.Column="1" Grid.Row="1" Margin="3" Text="{Binding Path=LastName}" />
</Grid>
Note You may be familiar with data source controls such as the BindingSource control in Windows Forms or the ObjectDataSource control in ASP.NET. These data sources often have the role of obtaining the data to be displayed in the user interface (handling the pulling process), which controls in the view can then bind to. No data source control is included in the standard control library in Silverlight. However, Chapter 5 covers the DomainDataSource control, which is a part of RIA Services, and discusses data source control type behavior.
For this example, we'll use the simple user interface created earlier in the previous workshop and walk through instantiating an object (in XAML), using that object as the data source of any bindings on the TextBox controls. We'll then bind the Text
property on the TextBox controls to it, which will display the object's property values and enable them to be updated by the user.
namespace Chapter02Workshop
{
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName
{
get { return FirstName + " " + LastName; }
}
}
}
MainPage.xaml
file we created in the previous example, add a person-Object
resource to your project that references the Person
class just created, setting some default values for its properties. You should have the following XAML:
<UserControl x:Class="Chapter02Workshop.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:my="clr-namespace:Chapter02Workshop"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400" >
<UserControl.Resources>
<my:Person x:Key="personObject" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition Width="190" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Margin="3" Text="First Name:"
VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="0" Margin="3" Name="firstNameTextBox" />
<TextBlock Grid.Column="0" Grid.Row="1" Margin="3" Text="Last Name:"
VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="1" Margin="3" Name="lastNameTextBox" />
<Button Grid.Column="1" Grid.Row="2" Margin="3" Name="OKButton" Content="OK"
Width="100" HorizontalAlignment="Right" />
</Grid>
</UserControl>
LayoutRoot
), and assign the personObject
resource to its DataContext
property:
<Grid x:Name="LayoutRoot" DataContext="{StaticResource personObject}">
Binding
markup expression. Here, we want to associate the FirstName
property of the person object with the Text
property of the TextBox to show the person's first name; we're using the Path
property of the Binding
markup expression and the LastName
property of the person object with the Text
property of the TextBox to show the person's last name:
<TextBox Grid.Column="1" Grid.Row="0" Margin="3"
Name="firstNameTextBox" Text="{Binding FirstName, Mode=TwoWay}" />
<TextBox Grid.Column="1" Grid.Row="1" Margin="3"
Name="lastNameTextBox" Text="{Binding LastName, Mode=TwoWay}" />
Note The default property of the Binding
markup expression is the Path
property, enabling you to specify a value for the Path
property without needing to explicitly specify which property you are assigning it to in the binding expression. This means that
Text="{Binding Path=FirstName, Mode=TwoWay}"
can instead be written as
Text="{Binding FirstName, Mode=TwoWay}"
You may have noted that we are explicitly setting the Mode
property on the binding expression. The binding mode specifies the direction of the data flow. There are three different binding modes:
OneTime
: This option enables the target to obtain a value from the source object, after which the binding is discarded, until a new object is assigned to the data context. Changes to the source object will not be propagated back to the target property, and changes to the target property's value in the user interface will not be propagated back to the source object. Use this option when the source object is guaranteed not to change.OneWay
: This option enables the target to obtain a value from the source object and be notified when that source value is updated so that the target can update itself with the new value. Changes to the source object will be propagated back to the target property, but changes to the target property's value in the user interface will not be propagated back to the source object. Use this option when the source object may change, but the value cannot be changed in the user interface, such as when binding to a TextBlock control.TwoWay
: This option enables the target to obtain a value from the source object, be notified when that source value is updated so that it can update itself with the new value, and update that value with changes made to the target. Changes to the source object will be propagated back to the target property, and changes to the target property's value in the user interface will be propagated back to the source object. Use this option when any changes made to the target in the user interface, such as the Text
property of the TextBox control, should update the source object accordingly.By default, data binding expressions use the OneWay
mode. Since changes made to the data in the user interface are not saved back to the source object with OneWay
mode, it is therefore particularly necessary for data input controls to explicitly specify TwoWay
mode on their binding expressions.
Note In order for changes made to a data-bound property in the code-behind to be propagated to the user interface, you will need to implement the INotifyPropertyChanged
interface on your class and call the PropertyChanged
event in the property's setter. Changes to the values of automatically implemented properties will not notify the user interface that they have been updated because, unfortunately, they do not support the INotifyPropertyChanged
interface, and hence any changes to them will not be reflected on the screen. However, dependency properties do automatically notify any controls bound to them when their value changes. Further discussion of this topic and examples of implementing the INotifyPropertyChanged
interface can be found in Chapter 7.
Any changes made in the text boxes in the user interface will update the properties on the source object that they're bound to automatically. You can prove this by updating the values in the text boxes and then displaying the corresponding property values from the bound object in a message box:
private void OKButton_Click(object sender, RoutedEventArgs e)
{
Person person = LayoutRoot.DataContext as Person;
MessageBox.Show("Hello " + person.FullName);
}
Note You can use many advanced options when binding, such as converting the data in the source object to something different before assigning it to the target (and vice versa) with a value converter, which is particularly useful for converting the data from one type to another, and specifying how invalid data exceptions are handled. These more advanced data binding concepts are covered in Chapter 11.
You will find that binding a control to a collection is a slightly different scenario than binding to a single object. Controls that expect a collection to bind to (for example, DataGrid, ComboBox, and ListBox) have a different property, often named ItemsSource
, that the items to be displayed in that control will be populated from; they don't obtain the collection from the DataContext
property.
For example, say you have a class representing a collection of Person
objects:
public class People : List<Person>
{
public People()
{
this.Add(new Person() { FirstName = "Homer", LastName = "Simpson" });
this.Add(new Person() { FirstName = "Marge", LastName = "Simpson" });
this.Add(new Person() { FirstName = "Bart", LastName = "Simpson" });
this.Add(new Person() { FirstName = "Lisa", LastName = "Simpson" });
this.Add(new Person() { FirstName = "Maggie", LastName = "Simpson" });
}
}
and you instantiate this class as a resource in XAML:
<UserControl.Resources>
<my:People x:Key="peopleCollection" />
</UserControl.Resources>
You can bind this resource to the ItemsSource
property of a ListBox
control and set its
DisplayMemberPath
property to the name of the property on the Person
object whose value will be displayed for each item in the list:
<ListBox ItemsSource="{StaticResource peopleCollection}" DisplayMemberPath="FullName" />
The ListBox
control takes this collection, and it creates a corresponding ListBoxItem
object for each Person
object in the People
collection, which it adds to its Items
collection. The most interesting part of this is that the DataContext
property of each ListBoxItem
is automatically assigned its corresponding Person
object from the collection. This allows you to identify the source object for a particular ListBoxItem
—when it is selected, for example—and enables you to template each item and bind to properties on that object.
Note Visual Studio 2010 provides a very good data binding expression builder from the Properties window, which can be very helpful when learning how to write data binding expressions. Visual Studio 2010 also has a great way to easily create forms, complete with bindings, from an object. Simply add your object as a new data source to the Data Sources tool window, select the type of controls the object and each of its properties should use, and drag it from the Data Sources tool window onto your design surface. You can then change the generated form as you see fit.