Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Artificial Inheritance Contexts in WPF

0.00/5 (No votes)
2 Jul 2008 2  
Reviews and compares three techniques that enable you to data bind to any element tree.

Introduction

This article discusses and demonstrates three ways to simulate having an inheritance context for DependencyObjects 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:

screenshot.png

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 DynamicResources 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()
    {
        // After setting our DataContext, inject it into the App's 
        // Resources so that it is visible to all DynamicResource references.
        // NOTE: This must be done *before* the call to InitializeComponent
        // since DynamicResource references for objects not in the logical tree
        // only check the App's Resources once, upon creation.
        // This only works once. After the call to InitializeComponent, updating
        // the resource value to a new datacontext object will have no effect.
        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>
          <!-- 
          Get a reference to the DataContext by grabbing it from
          the Application's Resources via a DynamicResource reference.
          -->
          <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 // Enable ElementName and DataContext bindings
{
    public DataContextSpy()
    {
        // This binding allows the spy to inherit a DataContext.
        BindingOperations.SetBinding(this, DataContextProperty, new Binding());

        this.IsSynchronizedWithCurrentItem = true;
    }

    /// <summary>
    /// Gets/sets whether the spy will return the CurrentItem of the 
    /// ICollectionView that wraps the data context, assuming it is
    /// a collection of some sort. If the data context is not a 
    /// collection, this property has no effect. 
    /// The default value is true.
    /// </summary>
    public bool IsSynchronizedWithCurrentItem { get; set; }

    public object DataContext
    {
        get { return (object)GetValue(DataContextProperty); }
        set { SetValue(DataContextProperty, value); }
    }

    // Borrow the DataContext dependency property from FrameworkElement.
    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()
    {
        // We are required to override this abstract method.
        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>
        <!-- 
        This object 'spies' on the DataContext for other elements. 
        -->
        <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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here