When we first discussed data binding in Chapter 2, you learned that a binding has both a source and a target, with the binding source obtained from the bound control's DataContext
property. The object assigned to the DataContext
property is inherited down through the object hierarchy, so an object assigned to the DataContext
property of a Grid control, for example, would automatically be available to all the controls contained within that Grid via their own DataContext
properties.
However, what if you wanted a property on a control to bind to something other than the control's DataContext
property, such as a resource, or a property on another control? Let's take a look at the different means available to achieve this in Silverlight.
If you set the Source
property of the binding, it will use the given object as the binding source instead of the object assigned to the DataContext
property of the control. If you're assigning this property in XAML, you will most likely be binding it to a resource, using the StaticResource
markup extension.
For example, let's say you have a Product
object that is defined as a resource, with productResource
as its key, somewhere up the object hierarchy. (Defining and instantiating a class as a resource for binding to will be discussed in the “Binding to a Resource” section later in this chapter.) The standard way of binding to this resource is by assigning it to the DataContext
property of the target control using the StaticResource
markup extension.For example, you can bind the Text
property of a TextBox control to the Name
property on the Product
object resource using the following XAML:
<TextBox DataContext="{StaticResource productResource}" Text="{Binding Name}" />
However, sometimes you want to bind a control's property to a given source, without altering the inherited data context for its child controls (where that data context has been set somewhere further up the hierarchy) or altering the binding source for its other properties. You can bind the Text
property of the TextBox control directly to the resource without changing the value of the TextBox control's DataContext
property to the resource by using the binding's Source
property. The following binding expression demonstrates how you do this, with the same result as the previous example:
<TextBox Text="{Binding Name, Source={StaticResource productResource}}" />
Note Alternatively, if you are creating the data binding in code, which will be discussed later in this chapter, you can assign any object to this property to act as the source of the binding.
In Chapters 5 and 6, we looked at how you can bind the ItemsSource
property of various controls to the Data
property of a DomainDataSource control using ElementName binding, but we haven't yet looked at this type of binding in any depth. The DomainDataSource control was retrieving data from the server and exposing that data publicly via its Data
property. We then consumed that data by binding the ItemsSource
property of our ListBox, DataGrid, or DataForm control to the DomainDataSource control's Data
property, essentially binding the property of one control to the property of another control. To bind a control's property to the property of another (named) control in the view like this, we needed to use a special kind of binding called ElementName binding. Using the ElementName
property on a binding, you could provide it the name of a control, within the same name scope, that would act as the source for the binding (instead of the object assigned to the control's DataContext
property). Let's take a look at some examples of employing ElementName binding to bind the properties of two controls together in XAML.
A very simple example is to link the Text
property of two TextBox controls. When you modify the text in one text box, the second text box's text will update automatically:
<StackPanel>
<TextBox Name="FirstTextBox" />
<TextBox Name="SecondTextBox" Text="{Binding Text, ElementName=FirstTextBox}" />
</StackPanel>
Note If you change the mode of the second text box's binding to TwoWay
, modifying the second text box's text will also update the first text box. That said, in this direction the first text box will be updated only when the second text box loses focus.
Similarly, we can display the current value of a Slider control in a text block by binding the Slider control's Value
property to the TextBlock control's Text
property:
<Slider Name="sourceSlider" />
<TextBlock Text="{Binding ElementName=sourceSlider, Path=Value}" />
The following XAML demonstrates taking the ItemsSource
property of a ListBox control (acting as the target) and binding it to the Data
property of a DomainDataSource control named productSummaryDDS
:
<ListBox ItemsSource="{Binding ElementName=productSummaryDDS, Path=Data}" />
This example demonstrates binding the IsBusy
property of the BusyIndicator control to the IsBusy
property of a DomainDataSource control named productSummaryDDS
. It will display the BusyIndicator control when the IsBusy
property of the DomainDataSource control is true:
<controlsToolkit:BusyIndicator
IsBusy="{Binding ElementName=productSummaryDDS, Path=IsBusy}" />
In this final example, we are binding the Target
property of the Label control to a text box. Note that, by not specifying a path for the binding, we are binding the Target
property to the text box itself, not a property on that text box:
<sdk:Label Content="Name:" Target="{Binding ElementName=ProductNameTextBox}" />
You may have noticed that there is a RelativeSource
property on the Binding
markup extension. This enables you to bind to a source relative to the target. You saw how ElementName binding enabled you to bind the property of one control to the property of another control. However, you can only use ElementName binding when the source control is named. RelativeSource binding allows you to bind to an unnamed source control relative to the target control.
You can obtain a reference to the source control using RelativeSource binding with the RelativeSource
markup extension, whose return value can then be assigned to the binding's RelativeSource
property. The RelativeSource
markup extension has three modes: Self,
TemplatedParent
, and FindAncestor
. Let's take a look at each of these in turn.
The Self
mode returns the target control and is useful for binding two properties on the same control. This is generally most useful when you have attached properties on a control that you want to bind to properties on the control itself. For example, perhaps you want to display a tooltip when the user hovers over a text box, which will show all the text in the text box. Here, we'll use the ToolTipService.ToolTip
attached property (demonstrated in Chapter 2) and bind it to the Text
property of the text box using the Self
mode of the RelativeSource
markup extension:
<TextBox Text="{Binding Path=CurrentItem}"
ToolTipService.ToolTip="{Binding Text,
RelativeSource={RelativeSource Self}}" />
The TemplatedParent
mode is applicable only when the control is contained within a control template or a data template. It returns the templated item and enables the target property to be bound to a property on that item. When used inside a data template, such as the data template for a ListBox item, the TemplatedParent
mode will return the content presenter for that templated item. Note that it doesn't actually return the ListBoxItem control; the data template is actually being applied to the content presenter of the item, so it's the content presenter that will be returned, unlike when using a control template to template the ListBoxItem, where the TemplatedParent
mode will return the ListBoxItem itself.
For example, the following binding expression in a data template will get the actual height of the content presenter:
"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ActualHeight}"
Another scenario where this binding may be useful in a data template is when you want to get the data context of the templated item. The data context is still inherited down the hierarchy in to data templates, but if you assign a different data context on a control in the data template, such as a Grid, you could use this method to obtain and bind to the original data context for the item again.
Note When used in a control template, the binding expression "{Binding RelativeSource={RelativeSource TemplatedParent}}"
is equivalent to the "{TemplateBinding}"
markup extension. Unlike the TemplateBinding
markup extension, which supports only a one-way binding, the RelativeSource
markup extension can be used to enable a two-way binding, which can be very useful in some scenarios when creating control templates for custom controls. This will be discussed further in Chapter 12.
Silverlight 5 has introduced a new mode for RelativeSource binding—FindAncestor
mode. FindAncestor
mode allows you to search for a control of a given type higher up in the XAML hierarchy. For example, say you have the following XAML:
<Grid Background="Green" Width="200" Height="200">
<Grid Background="Red" Margin="20">
<Grid Background="Blue" Margin="20">
<Grid Margin="20" />
</Grid>
</Grid>
</Grid>
As you can see, this XAML contains four nested Grid controls. Say that you now want to set the background color of the innermost Grid control to the same background color of one of the Grid controls further up the control hierarchy. We can use RelativeSource binding with the FindAncestor
mode to do so. The RelativeSource
markup extension has two properties that you use with this mode: AncestorType
and AncestorLevel
. The AncestorType
property is used to specify the type of control that you are looking for up the control hierarchy, and the AncestorLevel
property is used to specify how many times that control type should appear in the control hierarchy before that control is selected. The following XAML demonstrates binding the Background
property of the innermost Grid control to the Background
property of the top level Grid control, skipping two instances of the Grid control and taking the third:
<Grid Background="Green" Width="200" Height="200">
<Grid Background="Red" Margin="20">
<Grid Background="Blue" Margin="20">
<Grid Background="{Binding Background,
RelativeSource={RelativeSource FindAncestor,
AncestorType=Grid,
AncestorLevel=3}}"
Margin="20" />
</Grid>
</Grid>
</Grid>
If you change the value of the AncestorLevel
property to 1 or 2, you'll find that the background of the innermost Grid control will change to that of the corresponding Grid control in the control hierarchy.
Note The FindAncestor
mode of the RelativeSource
binding has many potential uses. For example, if you assign a ViewModel object to the DataContext
property of the view (i.e., on the Page or UserControl control), a control further down the control hierarchy whose DataContext
is not set to that ViewModel object—which will be the case if the control is contained within a ListBox item, for example—may still need to bind to a property on that ViewModel object. RelativeSource binding with FindAncestor
mode allows the binding to easily get a reference to the Page, UserControl, or other top level control, from which it can access its DataContext
property to get to the ViewModel object. Another good use of FindAncestor
is to enable controls within a list box item's data template to get a reference to the ListBoxItem control that they're contained within. This would enable the controls to access the ListBoxItem's IsSelected
property, allowing them to change their states depending on whether the list box item is selected or not.
You can bind a property of a control directly to the object assigned to its DataContext
property, either assigned directly to the property on the control or inherited down the object hierarchy, by simply setting its value to "{Binding}"
. This scenario is common when binding a number of controls to the same inherited data context. For example, the data context of a Grid control is bound to a collection, and multiple controls within that Grid inherit that data context and use it as their binding source. Therefore, to bind the ItemsSource
property of a ListBox control in the Grid to the collection, you can set its binding expression to "{Binding}"
.
As an example, the Text
property of the TextBox control in the following XAML is bound to the TextBox control's DataContext
property, resulting in the text box displaying “Hello” as its text:
<TextBox DataContext="Hello" Text="{Binding}" />
Note The "{Binding}"
binding expression is equivalent to {Binding Path=.}
, which is also the same as {Binding Path=}
.
Say that you have a view that handles events raised by the ViewModel object assigned to its DataContext
property. If a new ViewModel object is assigned to the view's DataContext
property, the view needs to unsubscribe from those events and handle the events on the new ViewModel object instead. In this scenario, the view needs to know when the value of its DataContext
property has changed so it can respond accordingly.
In earlier versions of Silverlight, you had no easy way of determining when the value of a control's DataContext
property had changed, making handling this scenario rather difficult. However, Silverlight 5 has introduced the DataContextChanged
event, which is raised on a control when the value of its DataContext
property is changed. You can handle this event and respond accordingly.
Let's take a look at an example demonstrating this event in action. The following XAML contains a Button control and a TextBox control. The Text
property of the TextBox control is bound to its DataContext
, and the TextBox control's DataContextChanged
event is handled in the code behind. The Button control's Click
event is also being handled in the code behind, as we'll use it to change the value of the TextBox control's DataContext
property:
<StackPanel>
<Button Name="ChangeContextButton" Content="Change Context"
Height="33" Width="143" Click="ChangeContextButton_Click" />
<TextBox Name="MyTextBox" Text="{Binding}"
DataContextChanged="MyTextBox_DataContextChanged" />
</StackPanel>
In the code behind, we now need to handle the Button control's Click
event, and the TextBox control's DataContextChanged
event. In the Button control's Click
event handler, change the value of the TextBox control's DataContext
property. The following code assigns a new GUID to it:
private void ChangeContextButton_Click(object sender, RoutedEventArgs e)
{
MyTextBox.DataContext = Guid.NewGuid();
}
In the TextBox control's DataContextChanged
event handler, we'll simply show a message box indicating that the event has been fired:
private void MyTextBox_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
MessageBox.Show("My data context has changed!");
}
When you run this, you'll find that the message box is shown every time you click the button. As an exercise, assign the GUID to the view's DataContext
property instead of the TextBox control's:
private void ChangeContextButton_Click(object sender, RoutedEventArgs e)
{
this.DataContext = Guid.NewGuid();
}
The TextBox control will inherit this data context, and thus the value of its DataContext
property will change when the value of the view's DataContext
property is changed. Therefore, its DataContextChanged
event will still be fired.
You've seen how you can bind to a property on bound objects, resources, and other controls in the view, but what if the source of the property you want to bind to is actually the view's code-behind class? Let's say you have the following XAML for a view:
<UserControl x:Class="Chapter11Workshop.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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<TextBlock Width="100" Height="20" />
</UserControl>
and the view's code behind defines a property named UserName
, like so:
using System.Windows.Controls;
namespace Chapter11Workshop
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
public string UserName
{
get { return "Chris Anderson"; }
}
}
}
The TextBlock control can use the FindAncestor
mode of the RelativeSource
binding to find the root element of the view and use it as the binding's source, like so:
<TextBlock Width="100" Height="20"
Text="{Binding UserName,
RelativeSource={RelativeSource FindAncestor,
AncestorType=UserControl}}" />
Alternatively, you can use ElementName binding to achieve the same result (though less elegantly) if you wish. Start by giving the root element in your XAML file a name, using its Name
property. In this example we have named it Root
. Now, you can simply use ElementName
binding to assign that element as its binding source:
<TextBlock Width="100" Height="20" Text="{Binding UserName, ElementName=Root}" />
Note Usually, any properties that your view needs to bind to will be defined in a ViewModel class (part of the MVVM design pattern, discussed in Chapter 13). However, this technique is particularly useful when creating user controls that expose properties to the host. The properties are defined in the code behind; the host sets their values; and the XAML can consume those values directly via these bindings without requiring the intervention of the code behind apart from getting or setting the property values. Custom controls have a slightly different means of handling this type of scenario, as will be discussed in Chapter 12.