Data binding is often thought of as an advanced topic, but there really is no reason for that. Data binding is
- Critical to writing XAML applications
- Not very difficult to learn
- A very powerful technique
The basic idea behind data binding couldn’t be simpler: you are going to provide values to UIElements
based on the values of objects or of other UIElements
.
Note UIElement
is a base class for most of the objects that have a visual appearance and can process input in Windows 8 applications.
Let’s take the first case first: binding an UIElement
to an object. The target of the binding must be an UIElement
, but the source of the binding can be any POCO (Plain Old CLR Object). In other words, the source can be just about anything.
Note When we talk about the target of binding, we mean the control that has a value bound to it. When we talk about the source of binding, we mean the object that has the value from which we will bind.
For example, if you have an object that represents a person, and that Person
object has two public properties, Name
and Age
, you may want to bind those properties to TextBlocks
so that you can display them easily without hard-coding the values. In that case, the Person
object (POCO) is the source and the TextBlocks
(UIElements
) are the targets of the binding.
To create this binding, you set the appropriate property (in this case Text
) of the UIElement
using the syntax of {binding <property>}
where <property>
is Name
, Age
, or whatever the public property is. The following code provides a very simple illustration. You begin by declaring an object to which you can bind; this is a POCO object.
class Employee
{
public string Name { get; set; }
public string Title { get; set; }
public Employee(string name, string title)
{
Name = name;
Title = title;
}
public static Employee GetEmployee()
{
var emp = new Employee( "Tom", "Developer" );
return emp;
}
}
As you can see, the Employee
class has two public properties. You bind to these properties in the XAML.
<StackPanel Name="xDisplay">
<StackPanel
Orientation="Horizontal">
<TextBlock
Text="Name:" />
<TextBlock
Margin="5,0,0,0"
Text="{Binding Name}" />
</StackPanel>
<StackPanel
Orientation="Horizontal">
<TextBlock
Text="Title:" />
<TextBlock
Margin="5,0,0,0"
Text="{Binding Title}" />
</StackPanel>
</StackPanel>
Note For this and all XAML examples, please place the XAML shown within the default Grid
on the MainPage
unless the example indicates otherwise.
The code indicates that you want to bind to the Name
and Title
properties, respectively, but of which object? It may be that there are many Employee
objects around at any given moment. The answer is found in the DataContext
. This can be set in the XAML or in the code; here you set it in code, in MainPage.xaml.cs:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
xDisplay.DataContext = Employee.GetEmployee();
}
The DataContext
says “I promised you a value in various properties. Please obtain those properties from this object.”
Every UIElement
has a DataContext
property. You can assign each individually, or you can, as I’ve done here, assign a DataContext
further up the visibility tree (in this case, on the StackPanel
). The DataContext
will be “inherited” by all the UIElements
further down the tree unless they have their own DataContext
, in which case it will override the ancestor’s DataContext
.
When data binding, you can designate one of three data binding modes:
- OneTime
- OneWay
- TwoWay
These determine whether and when the control is updated based on changes to the underlying data and vice versa. With OneTime binding, the control is not updated even if the underlying data changes. It is rare to use OneTime binding, but it can be useful in taking a snapshot of the state of a database at any given moment. With OneWay binding, the UI is updated when the underlying data changes, but updating the UI has no effect on the underlying data. With TwoWay binding, changes made to the underlying data are reflected in the UI and changes made by the user in the UI are reflected in the underlying data (and presumably persisted, for example to a database).
If you want TwoWay binding on the TextBlock
for Name
, you could use the following code:
Text="{Binding Name, Mode=TwoWay}” />
Note The default is OneWay binding.
Typically, TextBlocks
are bound with OneWay binding as they are read-only, and TextBoxes
(which are read/write) are bound with TwoWay binding.
Earlier I said that with OneWay binding (and TwoWay, for that matter), the UI is updated when the underlying data changes. This is true, but you have to help it along. You do so by having the class that you are binding to implement INotifyPropertyChanged
. This interface has only one event, PropertyChanged
. You raise this event each time a property is set in your class, and as a result, the UI is notified and updated.
The classic implementation tests to see if anyone has registered with the event, and if so raises the event using the object itself as the sender and creates a new NotifyPropertyEventArgs
object with the name of the property as the argument to the constructor.
Typically, this is all factored out to a helper method called RaisePropertyChanged
in the following code. The UI is dead simple—just TextBlocks
to hold the prompts for Name
and Title
, and more TextBlocks
to display the (bound) values. The button at the bottom of the StackPanel has an event handler, which will change the value of Name
(simulating a change coming from a server). Because of INotifyChanged
, when the value of Name
changes, it will be immediately reflected in the UI.
<StackPanel Name="LayoutRoot">
<StackPanel
Orientation="Horizontal">
<TextBlock
Text="Name" />
<TextBlock
Margin="5,0,0,0"
Height="50"
Width="200"
Text="{Binding Name}" />
</StackPanel>
<StackPanel
Orientation="Horizontal"
Margin="0,5,0,0">
<TextBlock
Text="Title" />
<TextBlock
Margin="5,0,0,0"
Height="50"
Width="200"
Text="{Binding Title}" />
</StackPanel>
<Button
Name="xChange"
Content="Change"
Margin="0,5,0,0"
Click="xChange_Click_1" />
</StackPanel>
The Employee
class implements INotifyPropertyChanged
.
Note As you can see, some of the terms in the class will have red squiggly lines under them. Place the cursor on that term, and type control-dot. Visual Studio will offer to add the missing namespace for you. Presto! Your code works.
class Employee : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged();
}
}
private string _title;
public string Title
{
get { return _title; }
set
{
_title = value;
RaisePropertyChanged();
}
}
private void RaisePropertyChanged(
[CallerMemberName] string caller = "" )
{
if ( PropertyChanged != null )
{
PropertyChanged( this, new PropertyChangedEventArgs( caller ) );
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Finally, the codebehind for MainPage.xaml
has the event handler for the button.
public sealed partial class MainPage : Page
{
Employee emp = new Employee() { Name = "George", Title = "President" };
public MainPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
LayoutRoot.DataContext = emp;
}
private void xChange_Click_1( object sender, RoutedEventArgs e )
{
emp.Name = "John";
}
}
When the Employee
is created, the name is set to George and the title to President, which is reflected in the UI. When the button is pushed, it simulates a change to the underlying data by changing the name to John. The UI is updated because the Employee
class implements INotifyPropertyChanged
.
Note In the calls to RaisePropertyChanged
, the name of the property being changed is not passed in. Yet the method is able to create the PropertyChangedEventArgs
with the calling method’s name. This is due to the attribute [CallerMemberName]
, which sets the caller argument to the name of the calling method. Most of the time this is just what you want, but if you need to override the value, you can pass in a text string that will be used instead.
Earlier I said that the source for data binding can be any CLR object. This includes UIElements
themselves. It is possible (and common!) to bind one UIElement
to a value in another. For example, you might bind the IsActive
property of a ProgressRing
to the IsChecked
property of a checkbox, as shown in the next example.
Note A ProgressRing
is used to show that work is being done when it is not known how long that work will take.
<StackPanel>
<StackPanel
Orientation="Horizontal"
HorizontalAlignment="Left"
>
<TextBlock
Text="ProgressRing:"
VerticalAlignment="Center"
Margin="0,0,20,0" />
<Border
BorderThickness="1"
BorderBrush="#44000000"
Padding="10">
<ProgressRing
x:Name="ProgressRing1"
IsActive="{Binding IsChecked, ElementName=ActiveCB}" />
</Border>
</StackPanel>
<CheckBox
Name="ActiveCB"
Content="Active?" />
</StackPanel>
Notice that this example has no codebehind. The ProgressRing
is active or not depending on the value of its IsActive
property. That property is bound to the IsChecked
property of the CheckBox
. When you check the CheckBox, the ProgressRing
becomes active.
At times the data in your business object (the source for your binding) and the target UIElement
may not have an exact type match. For example, if your Employee
class wants to keep track of the start date for each employee, a sensible way to do so is with a DateTime
object. However, when you display that data, you want to use a Text
object, and you may not want the entire default conversion of a DateTime
to a string. To rectify this problem, you can create a class that performs a conversion from one type to another (in this case, from DateTime
to string). This class will implement IValueConverter
and will have two methods: Convert
and ConvertBack
. In the following code, you modify the Employee
class to add the startDate
:
private DateTime _startDate;
public DateTime StartDate
{
get { return _startDate; }
set { _startDate = value; RaisePropertyChanged(); }
}
You then add a new converter class.
public class DateConverter : IValueConverter
{
public object Convert( object value, Type targetType, object parameter, string language )
{
DateTime date = (DateTime)value;
return date.ToString("d");
}
public object ConvertBack( object value, Type targetType, object parameter, string language
)
{
string strValue = value as string;
DateTime resultDateTime;
if ( DateTime.TryParse( strValue, out resultDateTime ) )
{
return resultDateTime;
}
throw new Exception( "Unable to convert string to date time" );
}
}
Note Notice the new use of formatting in date.ToString
. See http://msdn.microsoft.com/en-us/library/zdtaw1bw.aspx
.
You can now create a resource for this class in the XAML. Resources are reusable chunks of code. In this case, since this is a Page.Resource
, the code is reusable anywhere in this page. Place the following section below the page declaration but before the declaration of the grid:
Then you use that resource when you bind to the StartDate
.
<StackPanel
Orientation="Horizontal"
Margin="0,5,0,0">
<TextBlock
Text="Start Date" />
<TextBlock
Margin="5,0,0,0"
Height="50"
Width="200"
Text="{Binding StartDate, Converter={StaticResource DateToStringConverter } }" />
</StackPanel>
As a result, the StartDate
property is a DateTime
object in the Employee
object, but it is represented as a short string in the UI.
Often, rather than binding to a single object, you will want to bind to a collection of objects. There are a number of controls for handling collections, and you will examine them in coming chapters. For now, let’s focus on how the binding will work.
The trick is to teach the control how to display the bound data. You do this most often with a DataTemplate
, a template or set of XAML that will be reproduced for each member of the collection. In the following code, you create a slightly modified Employee
class. Each Employee
has two properties, Name
and Title
. You also give the class a static method that returns a list of employees, simulating retrieving data from a web service or other data source.
class Employee : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged();
}
}
private string _title;
public string Title
{
get { return _title; }
set
{
_title = value;
RaisePropertyChanged();
}
}
private void RaisePropertyChanged(
[CallerMemberName] string caller = "" )
{
if ( PropertyChanged != null )
{
PropertyChanged( this, new PropertyChangedEventArgs( caller ) );
}
}
public event PropertyChangedEventHandler PropertyChanged;
public static ObservableCollection<Employee> GetEmployees()
{
var employees = new ObservableCollection<Employee>();
employees.Add( new Employee() { Name = "Washington", Title = "President 1" } );
employees.Add( new Employee() { Name = "Adams", Title = "President 2" } );
employees.Add( new Employee() { Name = "Jefferson", Title = "President 3" } );
employees.Add( new Employee() { Name = "Madison", Title = "President 4" } );
employees.Add( new Employee() { Name = "Monroe", Title = "President 5" } );
return employees;
}
}
Notice that the collection type you use for Employee
is an ObservableCollection
. This type implements INotifyPropertyChanged
and INotifyCollectionChanged
, and so will inform the UI that the collection has changed. Note that to ensure notification if an individual element in the collection is changed (e.g., an employee’s name changes), you must also implement INotifyPropertyChanged
on the element type itself.
All you need now is to bind this collection of employees to a control that handles collections of data, such as a ComboBox
. The ComboBox
has no way of knowing, however, how to display an Employee
. Left to its own devices, it will just display whatever ToString
resolves to. You could override ToString
, but it is much more efficient and flexible to teach the ComboBox
how to display the Employee
exactly as you want, and you do that with a DataTemplate
, as shown in the following code:
<ComboBox
x:Name="ComboBox1"
ItemsSource="{Binding}"
Foreground="Black"
FontSize="30"
Height="50"
Width="550">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel
Orientation="Horizontal"
Margin="2">
<TextBlock
Text="Name:"
Margin="2" />
<TextBlock
Text="{Binding Name}"
Margin="2" />
<TextBlock
Text="Title:"
Margin="10,2,0,2" />
<TextBlock
Text="{Binding Title}"
Margin="2" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
The ItemTemplate
controls how each item is displayed and the DataTemplate
lays out how the data is displayed. In this case, you are displaying the data by laying out four TextBlocks
horizontally so that each Employee
appears on a single line in the ComboBox
. Notice that you bind to the ItemsSource
property with the keyword binding but you don’t specify what you’re binding to. This is done by setting the DataContext
in the codebehind.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
ComboBox1.ItemsSource = Employee.GetEmployees();
}
You set this in the codebehind because you are mimicking the action of retrieving the resource (Employees
) from a database or other service. The result is that when the ComboBox
is opened, all the Employee
objects are displayed, each according to the DataTemplate
, as seen in Figure 2-1.
Note President Kennedy was entertaining a roomful of Nobel Prize winners when he said, “I think this is the most extraordinary collection of talent, of human knowledge, that has ever been gathered at the White House—with the possible exception of when Thomas Jefferson dined alone.”