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 Binding
s. 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...
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!