Assigning the Source of a Binding

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.

Using the Binding's Source Property

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}}" />

images 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.

ElementName 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>

images 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}" />

RelativeSource Binding

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.

Self Mode

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}}" />

TemplatedParent Mode

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.

images 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.

FindAncestor Mode

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.

images 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.

Binding a Control Property Directly to Its Data Context

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}" />

images Note The "{Binding}" binding expression is equivalent to {Binding Path=.}, which is also the same as {Binding Path=}.

Detecting When the DataContext's Value Has Changed

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.

Binding to a Property in the View's Code Behind

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}" />

images 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset