My blog about software development on the Microsoft® stack.

Binding To a Parent Element in UWP and WinUI 3

In a WPF application, it’s easy to bind to a property of a parent element in the visual tree by setting the RelativeSource property of the binding to a RelativeSource object that specifies the type of ancestor to bind to.

For example, the following XAML markup binds the Text property of the TextBlock to the Tag property of the parent window:

<Window ... Tag="aWindow">
    <StackPanel>
        <TextBlock Text="{Binding Tag, RelativeSource={RelativeSource AncestorType=Window}}" />
...

The above XAML attribute syntax is a shorthand for using the following object element syntax:

<TextBlock>
    <TextBlock.Text>
        <Binding Path="Tag">
            <Binding.RelativeSource>
                <RelativeSource
                    Mode="FindAncestor"
                    AncestorType="Window" />
            </Binding.RelativeSource>
        </Binding>
    </TextBlock.Text>
</TextBlock>

When you migrate your application to UWP or WinUI 3, there is indeed a RelativeSource property and object available but it doesn’t support the FindAncestor mode.

In some cases, you could set the ElementName property of the binding to the name of the ancestor element to bind to instead of using a RelativeSource:

<TextBlock Text="{Binding Tag, ElementName=aWindow}" />

In other cases, namely when the target and source elements reside in different XAML namescopes or when the source element has been created dynamically, this doesn’t work.

Consider the following sample XAML markup where the intention is to bind the Text property of a TextBlock in the ItemTemplate of an ItemsRepeater to the Tag property of the latter. This binding will fail:

<ItemsRepeater x:Name="ir" Tag="aTag">
    <ItemsRepeater.ItemTemplate>
        <DataTemplate>
            <!-- NOTE; This binding is invalid: -->
            <TextBlock Text="{Binding Tag, ElementName=ir}" />
        </DataTemplate>
    </ItemsRepeater.ItemTemplate>
</ItemsRepeater>

A workaround to be able to bind to a property of an ancestor in the visual tree is to create an attached property that sets the DataContext of the target element (the TextBlock in this case) to a parent element of a specific type. Here is a sample implementation of such a property:

public static class AncestorSource
{
    public static readonly DependencyProperty AncestorTypeProperty =
        DependencyProperty.RegisterAttached(
            "AncestorType",
            typeof(Type),
            typeof(AncestorSource),
            new PropertyMetadata(default(Type), OnAncestorTypeChanged)
    );

    public static void SetAncestorType(FrameworkElement element, Type value) =>
        element.SetValue(AncestorTypeProperty, value);

    public static Type GetAncestorType(FrameworkElement element) =>
        (Type)element.GetValue(AncestorTypeProperty);

    private static void OnAncestorTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        FrameworkElement target = (FrameworkElement)d;
        if (target.IsLoaded)
            SetDataContext(target);
        else
            target.Loaded += OnTargetLoaded;
    }

    private static void OnTargetLoaded(object sender, RoutedEventArgs e)
    {
        FrameworkElement target = (FrameworkElement)sender;
        target.Loaded -= OnTargetLoaded;
        SetDataContext(target);
    }

    private static void SetDataContext(FrameworkElement target)
    {
        Type ancestorType = GetAncestorType(target);
        if (ancestorType != null)
            target.DataContext = FindParent(target, ancestorType);
    }

    private static object FindParent(DependencyObject dependencyObject, Type ancestorType)
    {
        DependencyObject parent = VisualTreeHelper.GetParent(dependencyObject);
        if (parent == null)
            return null;

        if (ancestorType.IsAssignableFrom(parent.GetType()))
            return parent;

        return FindParent(parent, ancestorType);
    }
}

When the dependency property is set to a Type, the OnAncestorTypeChanged property changed callback will be invoked. It waits until the target element (which again is the TextBlock in this particular example) has been loaded and then uses the VisualTreeHelper.GetParent method to get a reference to first parent element of the specified type. Finally, it sets the DataContext of the target element to the element returned by the FindParent method.

Here is how the modified XAML markup that uses the custom attached property would look like:

<ItemsRepeater ItemsSource="{Binding Items}" Tag="aTag">
    <ItemsRepeater.ItemTemplate>
        <DataTemplate>
            <TextBlock local:AncestorSource.AncestorType="ItemsRepeater"
                       Text="{Binding Tag}" />
        </DataTemplate>
    </ItemsRepeater.ItemTemplate>
</ItemsRepeater>

The binding to the Tag property of the parent ItemsRepeater now works as expected as the attached property programmatically sets the DataContext of the TextBlock to the parent ItemsRepeater and the Text property of the TextBlock is bound directly to the Tag property of its current DataContext.

There is one caveat with this approach. If you intend to bind several properties of the same target element to different source objects, setting the DataContext of the target element won’t really work.

Consider the following WPF example where the ItemTemplate contains a Button that executes a command of the parent view model and passes a reference to the current item in the ListBox as a parameter to that command:

<ListBox ItemsSource="{Binding Items}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Button Content="Save"
                    Command="{Binding DataContext.SaveCommand,
                        RelativeSource={RelativeSource AncestorType=ListBox}}"
                    CommandParameter="{Binding}"/>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

The Command property is bound to a SaveCommand property of the DataContext of the ListBox and the CommandParameter property is bound to the current object itself in the Items collection.

If you want to migrate this Button to an ItemsRepeater in UWP or WinUI 3, you could introduce an invisible element that uses the attached property from above to “capture” a reference to the parent ItemsRepeater. You then use the ElementName property to bind the Command property of the Button to the captured ancestor element’s DataContext:

<ItemsRepeater ItemsSource="{Binding Items}">
    <ItemsRepeater.ItemTemplate>
        <DataTemplate>
            <Grid>
                <TextBlock x:Name="proxy" local:AncestorSource.AncestorType="ItemsRepeater" />
                <Button Content="Save"
                        Command="{Binding DataContext.DataContext.SaveCommand, ElementName=proxy}"
                        CommandParameter="{Binding}">
                </Button>
            </Grid>
        </DataTemplate>
    </ItemsRepeater.ItemTemplate>
</ItemsRepeater>

If you want to try this out for yourself, I’ve uploaded the sample code to GitHub. It targets version 1.0 preview 3 (1.0.0-preview3) of the Windows App SDK which was latest release of the preview channel for version 1.0 of the Windows App SDK available at the time of writing this in the middle of January 2022.


One Comment on “Binding To a Parent Element in UWP and WinUI 3”

  1. Misha says:

    Excellent solution. Thank you very much!


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s