Introduction
This article discusses and demonstrates three ways to simulate having an inheritance context for DependencyObject
s external to an element tree. In this article, we examine how to use artificial inheritance contexts to enable data binding to work in situations that would not work otherwise. This article’s demo program shows how to use all three techniques. Toward the end of this article, there is a section comparing the pros and cons of each technique.
Background
You can register a dependency property with the WPF property system so that its value inherits down the element tree, technically the logical tree. The canonical example is the FontSize
property. If you set FontSize
on a Window
, all elements in that Window
will display text at that size. If you specify a different FontSize
on, say, a GroupBox
in the Window
, all of the elements in the GroupBox
will inherit and use that new FontSize
instead of the Window
’s FontSize
. This is similar to ambient properties in Windows Forms.
Another inheritable dependency property on both the FrameworkElement
and FrameworkContentElement
classes is DataContext
, which acts like an ambient, implicit data source for all data bindings in an element tree. When a property of an element is bound and the Binding
’s Source
, RelativeSource
, and ElementName
properties are not set, the Binding
automatically binds to the element's DataContext
. This is a powerful feature of the WPF framework because, in practice, most bindings are between an element property and a property on the data context object. This is one reason why WPF is a great data-driven user interface platform.
This system breaks down when you try to bind a property on an object that is not in the element tree. One example of this is if you try to bind a property on the object referenced by an element’s Tag
property. You can set an element’s Tag
to a visual element, but that element is not in an element tree. WPF will not add that element to an element tree because it is not actually part of the user interface; it is just some visual element sitting in memory. Since the element is not in an element tree, it does not have an inheritance context, which means that it cannot bind to an inherited DataContext
. The key here is that when an element is not in the element tree, it cannot inherit dependency property values. Value inheritance relies on what is known as an “inheritance context,” which is the internal infrastructure used to propagate values down an element tree. In addition, not having an inheritance context prevents bindings from being able to use the ElementName
property to specify their source.
This article shows three ways to work around the problem of not having an inheritance context when data binding. Each technique manages to “export” an element tree’s DataContext
to objects external to the tree.
The Demo
At the top of this article, you can download the demo project that accompanies this article. The app shows how to do the same task three ways. The task itself is rather trivial, and you could definitely implement it without the need for artificial inheritance contexts. However, as with many of my articles, I struggled to come up with a programming task that is simple enough to not “get in the way” yet complicated enough to allow me to demonstrate the technique under review.
The demo app allows the user to choose a historic document, such as the US Constitution, and view its name displayed in large text. The document name paints with a brush that displays a photograph of the document. The UI also contains a ListBox
with two options. If you select “Fully Opaque”, the photo is displayed at full opacity; otherwise, if you select “Semi-Transparent”, it displays at half opacity.
As seen in the screenshot below, when viewing the US Constitution, the text paints with a photo of the US Constitution at full opacity:
As I mentioned before, this simple (and bizarre!) application could easily be created without using an artificial inheritance context. Most real-world situations that require an artificial inheritance context are more complicated and obscure than this stupid little programming task.
Resource Injection
The quickest and least complicated way to gain access to a DataContext
, or any other object for that matter, from elements not in an element tree is to inject it into the resource system. The trick here is to rely on the fact that a DynamicResource
reference will check the Application
’s Resources
collection, upon creation, for a resource with a matching resource key. This even occurs if the DynamicResource
is on a property of an element that is not in an element tree. Note, however, that unlike normal DynamicResource
references, a subsequent update made to the resource in App.Resources
will not be noticed and honored. In essence, they act like StaticResource
references in this situation.
The other trick to know is that the resource must be added to App.Resources
before the DynamicResource
s are created. In practice, this usually means that you must load your data context object and put it into App.Resources
before the Window
/Page
/UserControl
constructor calls InitializeComponent
. This ensures that the one-time check for a matching resource in App.Resources
made by each DynamicResource
reference will be successful.
Here is the code-behind for the ResourceInjectionDemo
UserControl
:
public partial class ResourceInjectionDemo : UserControl
{
public ResourceInjectionDemo()
{
base.DataContext = HistoricDocument.GetDocuments();
App.Current.Resources["DATA_HistoricDocuments"] = base.DataContext;
this.InitializeComponent();
}
}
The resource key “DATA_HistoricDocuments
” is an arbitrary identifier that I made up. You will see it in use in the control’s XAML file:
<UserControl
x:Class="ArtificialInheritanceContextDemo.ResourceInjectionDemo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ComboBox
Grid.Row="0"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
Margin="4"
/>
<ListBox x:Name="_listBox" Grid.Row="1" Margin="4">
<ListBoxItem Content="Fully Opaque" IsSelected="True">
<ListBoxItem.Tag>
-->
<Image
DataContext="{DynamicResource DATA_HistoricDocuments}"
Opacity="1"
Source="{Binding PhotoUri}"
Width="300" Height="350"
/>
</ListBoxItem.Tag>
</ListBoxItem>
<ListBoxItem Content="Semi-Transparent">
<ListBoxItem.Tag>
<Image
DataContext="{DynamicResource DATA_HistoricDocuments}"
Opacity="0.5"
Source="{Binding PhotoUri}"
Width="300" Height="350"
/>
</ListBoxItem.Tag>
</ListBoxItem>
</ListBox>
<Viewbox Grid.Row="2" Stretch="Fill">
<TextBlock
FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center"
Text="{Binding Path=Name}"
>
<TextBlock.Foreground>
<VisualBrush
Visual="{Binding ElementName=_listBox, Path=SelectedItem.Tag}"
/>
</TextBlock.Foreground>
</TextBlock>
</Viewbox>
</Grid>
</UserControl>
DataContextSpy
A spy is a person who secretly examines the actions and information of other individuals or organizations and reports it to an external party. My DataContextSpy
class does exactly that, only it observes the DataContext
of an element tree, and external elements can bind against it to gain access to the DataContext
. You simply add a DataContextSpy
to the Resources
collection of any element, except the element on which the DataContext
was set, and its DataContext
property will automagically expose the DataContext
of that element.
Here is the DataContextSpy
class:
public class DataContextSpy
: Freezable {
public DataContextSpy()
{
BindingOperations.SetBinding(this, DataContextProperty, new Binding());
this.IsSynchronizedWithCurrentItem = true;
}
public bool IsSynchronizedWithCurrentItem { get; set; }
public object DataContext
{
get { return (object)GetValue(DataContextProperty); }
set { SetValue(DataContextProperty, value); }
}
public static readonly DependencyProperty DataContextProperty =
FrameworkElement.DataContextProperty.AddOwner(
typeof(DataContextSpy),
new PropertyMetadata(null, null, OnCoerceDataContext));
static object OnCoerceDataContext(DependencyObject depObj, object value)
{
DataContextSpy spy = depObj as DataContextSpy;
if (spy == null)
return value;
if (spy.IsSynchronizedWithCurrentItem)
{
ICollectionView view = CollectionViewSource.GetDefaultView(value);
if (view != null)
return view.CurrentItem;
}
return value;
}
protected override Freezable CreateInstanceCore()
{
throw new NotImplementedException();
}
}
This class uses the Hillberg Freezable Trick to gain access to the DataContext
of the host element. That trick relies on the fact that WPF’s Freezable
class has built-in support for getting an inheritance context, even though it is not in the element tree. For more information about how that works, I recommend you check out Dr. WPF’s thorough explanation here.
The DataContextSpyDemo
UserControl
has no logic in the code-behind, so let’s jump straight to the XAML to see how it works:
<UserControl
x:Class="ArtificialInheritanceContextDemo.DataContextSpyDemo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ArtificialInheritanceContextDemo"
>
<UserControl.DataContext>
<ObjectDataProvider
MethodName="GetDocuments"
ObjectType="{x:Type local:HistoricDocument}"
/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ComboBox
Grid.Row="0"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
Margin="4"
/>
<ListBox x:Name="_listBox" Grid.Row="1" Margin="4">
<ListBox.Resources>
-->
<local:DataContextSpy x:Key="spy" />
</ListBox.Resources>
<ListBoxItem Content="Fully Opaque" IsSelected="True">
<ListBoxItem.Tag>
<Image
DataContext="{Binding Source={StaticResource spy}, Path=DataContext}"
Opacity="1"
Source="{Binding PhotoUri}"
Width="300" Height="350"
/>
</ListBoxItem.Tag>
</ListBoxItem>
<ListBoxItem Content="Semi-Transparent">
<ListBoxItem.Tag>
<Image
DataContext="{Binding Source={StaticResource spy}, Path=DataContext}"
Opacity="0.5"
Source="{Binding PhotoUri}"
Width="300" Height="350"
/>
</ListBoxItem.Tag>
</ListBoxItem>
</ListBox>
<Viewbox Grid.Row="2" Stretch="Fill">
<TextBlock
FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center"
Text="{Binding Path=Name}"
>
<TextBlock.Foreground>
<VisualBrush
Visual="{Binding ElementName=_listBox, Path=SelectedItem.Tag}"
/>
</TextBlock.Foreground>
</TextBlock>
</Viewbox>
</Grid>
</UserControl>
Virtual Branch of Logical Tree
The last technique we cover is something that I wrote an article about back in May 2007. I included a demonstration that uses a virtual branch in this article just for the sake of completeness. Creating a virtual branch of the logical tree is similar to using a DataContextSpy
, only the element tree pushes the DataContext
, instead of the DataContext
being pulled from the element tree. The basic idea is that we export the DataContext
property (or any property) via static resource references and a OneWayToSource
binding.
Virtual branches are very flexible, and you can easily use them to export more than just the DataContext
property of an element tree. The big downside, though, is that you must set up a OneWayToSource
binding for the exported properties on the element which has that property setting applied to it. Whereas a DataContextSpy
can be added to the Resources
collection of any element except the one on which the DataContext
was set, a virtual branch can only be established by the element on which the DataContext
was set.
Here is the code-behind for the VirtualBranchDemo
UserControl
:
public partial class VirtualBranchDemo : UserControl
{
public VirtualBranchDemo()
{
this.InitializeComponent();
base.DataContext = HistoricDocument.GetDocuments();
}
}
The DataContext
of the control is set in code because there is a binding in XAML that pushes the property value over to the virtual branch. The XAML file is shown below:
<UserControl
x:Class="ArtificialInheritanceContextDemo.VirtualBranchDemo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ArtificialInheritanceContextDemo"
>
<UserControl.Resources>
<FrameworkElement x:Key="bridge" />
</UserControl.Resources>
<UserControl.DataContext>
<Binding
Mode="OneWayToSource"
Path="DataContext"
Source="{StaticResource bridge}"
/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ComboBox
Grid.Row="0"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
Margin="4"
/>
<ListBox x:Name="_listBox" Grid.Row="1" Margin="4">
<ListBoxItem Content="Fully Opaque" IsSelected="True">
<ListBoxItem.Tag>
<Image
DataContext="{Binding Source={StaticResource bridge}, Path=DataContext}"
Opacity="1"
Source="{Binding PhotoUri}"
Width="300" Height="350"
/>
</ListBoxItem.Tag>
</ListBoxItem>
<ListBoxItem Content="Semi-Transparent">
<ListBoxItem.Tag>
<Image
DataContext="{Binding Source={StaticResource bridge}, Path=DataContext}"
Opacity="0.5"
Source="{Binding PhotoUri}"
Width="300" Height="350"
/>
</ListBoxItem.Tag>
</ListBoxItem>
</ListBox>
<Viewbox Grid.Row="2" Stretch="Fill">
<TextBlock
FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center"
Text="{Binding Path=Name}"
>
<TextBlock.Foreground>
<VisualBrush
Visual="{Binding ElementName=_listBox, Path=SelectedItem.Tag}"
/>
</TextBlock.Foreground>
</TextBlock>
</Viewbox>
</Grid>
</UserControl>
Pros and Cons of Each Technique
Each of these techniques has its own relative merits. I have listed all of the pros and cons that I could think of here to help make it easier to decide which approach to use. I am sure there must be other considerations that I have not listed, so please drop a comment on this article if you discover some.
Resource Injection
Pros
- Easy to implement
- Easy to understand
Cons
- Pollutes
App.Resources
with global variables
- Cannot set the
DataContext
to a new value
- Requires the
DataContext
object to exist before InitializeComponent
is called
DataContextSpy
Pros
- Easy to implement
- New
DataContext
will be honored
DataContext
can be bound via ElementName
, if necessary
- Introduces very little to no impact on the element tree
Cons
- Potentially confusing
- Cannot be added to
Resources
of the element where the DataContext
is set
- Spying on other properties requires new properties on the
DataContextSpy
class
Virtual Branch
Pros
- New
DataContext
will be honored
- Easily export properties other than
DataContext
to a virtual branch via data binding
Cons
- Potentially confusing
- Requires the
DataContext
bridge to be bound by the element where the DataContext
is set
- Requires the element tree to export the
DataContext
(has impact on the element tree)
Revision History
- July 2, 2008 – Created the article.