The blog of dlaa.me

Hopping outside the box [How to: Leapfrog Bindings (bridge DataContexts) in Silverlight and WPF]

The data binding infrastructure for Silverlight and WPF is extremely powerful and makes it easy to write powerful, dynamic applications without worrying about synchronizing the UI with every change to the underlying data. By leveraging this power, developers can think entirely in terms of the data model and be confident that any changes they make will be reflected by the interface automatically.

The way this works is that every element has a DataContext property, and the value of that property is inherited. DataContext can be assigned any object and acts as the default source for all Bindings applied to the element's properties. The object acting as DataContext can be as simple or complex as an application needs and is free to "shape" the data however is most convenient for the UI and Bindings. Collection-based controls like ItemsControl (and its subclasses ListBox, TreeView, etc.) automatically set the DataContext of their containers to the specific item they represent. This is what you'd expect and enables a great deal of flexibility when displaying collections. However, each element has a only one DataContext - so sometimes it's useful to "reach outside" that and bind to properties on some other object.

The good news is that there's an easy way to leapfrog the DataContext and "bridge" the two elements! The trick is to use an ElementName Binding to point to that other element - then to root the Binding's Path at that element's DataContext and "dot-down" to the property of interest. An example should make things clearer...

 

LeapFroggingDataContexts sample

The sample above has an application-wide DataContext model object ApplicationViewModel containing a bool property ShowHeight that's TwoWay-bound to the CheckBox control. For convenience, ShowHeight is also exposed as a Visibility value via HeightVisibility - which is much more convenient to bind the Visibility property to. There's also a Mountains property containing a collection of MountainViewModel model objects. Each mountain in the list is meant to show or hide its height according to the setting on ApplicationViewModel - but because the DataContext of the ListBoxItem containers is set to instances of MountainViewModel, they can't "see" the HeightVisibility property... The most direct option is probably to propagate this value "down" to the MountainViewModel instances by adding a property to the class for that purpose and keeping it in sync with the "real" property on ApplicationViewModel.

However, that feels like a bit of overkill for the current situation. Instead, if we just "leapfrog" the Binding in the item content, we can make use of the ApplicationViewModel directly and everything stays nice and simple!

 

[Click here to download the Silverlight 4 source code for the LeapFroggingDataContexts sample.]

 

Here's what it looks like in XAML:

<UserControl x:Class="LeapFroggingDataContexts.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:LeapFroggingDataContexts">

    <UserControl.Resources>
        <local:ApplicationViewModel x:Key="ApplicationViewModel"/>
    </UserControl.Resources>

    <Grid
        x:Name="LayoutRoot"
        DataContext="{StaticResource ApplicationViewModel}"
        Width="150"
        HorizontalAlignment="Center"
        VerticalAlignment="Center">

        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition/>
        </Grid.RowDefinitions>

        <CheckBox
            Grid.Row="0"
            Content="Show height"
            IsChecked="{Binding ShowHeight, Mode=TwoWay}"/>

        <ListBox
            Grid.Row="1"
            ItemsSource="{Binding Mountains}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Name}"/>
                        <TextBlock
                            Text="{Binding Height, StringFormat=' [{0:n0}m]'}"
                            Visibility="{Binding DataContext.HeightVisibility, ElementName=LayoutRoot}"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</UserControl>

ElementName points the Binding at an element with the DataContext of interest, then the Path selects the DataContext as a starting point (an instance of the ApplicationViewModel class in this case), hooks up to that object's HeightVisibility property - and we're done!

 

While architectural purists might argue this technique is bad form, I've seen it come in handy for enough customer scenarios that it seems a worthwhile approach to keep in mind. Please don't abuse it - but feel free to use it! :)