Binding to alternate DataContexts

The DataContext is one of the most important parts of the WPF data binding system, especially in MVVM applications. Being built into the Binding type as the common source for bindings in a specific scope reduces plumbing code needed and makes XAML more concise. Unfortunately, for any given element there is only one DataContext available to bind against. Most of the time this isn’t a problem, but complex data hierarchies often lead to situations where an element needs to bind to the DataContext of some parent element in addition to its own local DataContext.

I most often run into this with ItemsControls where something inside the ItemTemplate needs to bind to the ViewModel containing the entire collection:

<ItemsControl ItemsSource="{Binding Items}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <DockPanel>
                <TextBlock Text="{Binding Name}"/>
                <Button Content="Remove" Command="<Remove item command on parent VM>" CommandParameter="{Binding}"/>
            </DockPanel>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>


Some people will use an ElementName binding to access the parent VM but I prefer using a RelativeSource FindAncestor binding to avoid name scoping issues. DataContext also needs to be added to the Path:

<ItemsControl ItemsSource="{Binding Items}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <DockPanel>
                <TextBlock Text="{Binding Name}"/>
                <Button Content="Remove" CommandParameter="{Binding}"
                        Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemsControl}}, Path=DataContext.RemoveItemCommand}"/>
            </DockPanel>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

In addition to making for a long Binding statement, this approach can also break down in some situations, like when binding inside a ContextMenu, ToolTip, or other Popup that exists outside the main visual tree. This method can also fail when used with ContentControl ContentTemplates or some other container type where the FindAncestor specification isn’t as straightforward as finding the first ItemsControl. Similar problems exist with ElementName.

To get around these situations I will often define a new inherited attached property that I can use to pass a specific property or a whole DataContext down through the logic tree by setting a binding on the parent and then accessing it with a RelativeSource Self and another complex Path statement:

<ItemsControl ItemsSource="{Binding Items}" local:MyExtensions.PassthroughData="{Binding}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <DockPanel>
                <TextBlock Text="{Binding Name}"/>
                <DockPanel.ContextMenu>
                    <ContextMenu>
                        <MenuItem Header="Remove" CommandParameter="{Binding}"
                                  Command="{Binding RelativeSource={RelativeSource Self}, Path=(local:MyExtensions.PassthroughData).RemoveItemCommand}"/>
                    </ContextMenu>
                </DockPanel.ContextMenu>
            </DockPanel>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

This method is less fragile than the FindAncestor binding, but is still very verbose and lacks the easy readability of a binding against DataContext, especially for a less experienced developer who might not be familiar with the Path syntax for attached properties. I also found myself defining this same pattern in multiple places for different usages. By using a more generic attached property and a custom MarkupExtension I can get the same experience in the XAML as a normal DataContext binding, but now with two different objects to choose from.

<ItemsControl ItemsSource="{Binding Items}" FrameworkElementExtensions.IsContextContainer="True">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <DockPanel>
                <TextBlock Text="{Binding Name}"/>
                <DockPanel.ContextMenu>
                    <ContextMenu>
                        <MenuItem Header="Remove" CommandParameter="{Binding}"
                                  Command="{ContainerBinding RemoveItemCommand}"/>
                    </ContextMenu>
                </DockPanel.ContextMenu>
            </DockPanel>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

The zip file contains a library project with the class defining the attached properties and the ContainerBinding for binding to the container DataContext. Since the ProvideValue method on Binding is sealed, the custom binding uses a base class that duplicates and wraps all of Binding’s properties and leaves ProvideValue unsealed. This allows ContainerBinding to use any of the other options available on a standard Binding.

ExtensionLibrary.zip

Leave a Reply

Your email address will not be published. Required fields are marked *