Skip to content

RelativeSource FindAncestor binding errors #3853

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
JorisCleVR opened this issue May 28, 2025 · 4 comments
Open

RelativeSource FindAncestor binding errors #3853

JorisCleVR opened this issue May 28, 2025 · 4 comments
Assignees

Comments

@JorisCleVR
Copy link
Contributor

Bug explanation

I am currently having a lot of binding errors. For example:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.FrameworkElement', AncestorLevel='1''. BindingExpression:Path=(0); DataItem=null; target element is 'GroupBox' (Name=''); target property is 'Foreground' (type 'Brush')

Some context
Earlier I had them in some occasions, but was not able to find the root cause of them. But since I now enabled virtualization on all of my ListBoxes I am getting them more cinsitently. And it became even more reproducable now that I am introducing a LazyLoadContentControl.

For the completeness: What the LazyLoadContentControl does is it first loads a more leightweight placeholder control (defined in a DataTemplate). Then when the GUI has time (using the GuiDispatcher with DispatcherPriority.Background) it renders the (complex) actual control (also defined in a DataTemplate). The results of this control are great and it feels the same as most web applications do with loading their contents.

Reference code example (simplified)
The following example is a simplified version of the code. Note it can not actually be run, but is meant as a reference to get a more clear understanding of how the MaterialDesignCardGroupBox style causing the error is used

<ListBox Style="{StaticResource MaterialDesignListBox}" Margin="0" ItemsSource="{Binding Projects}" VirtualizingPanel.IsVirtualizin="True" VirtualizingPanel.VirtualizationMode="Recycling" VirtualizingPanel.IsVirtualizingWhenGrouping="True" VirtualizingPanel.ScrollUnit="Pixel"
         VirtualizingPanel.CacheLengthUnit="Item" VirtualizingPanel.CacheLength="10"
HorizontalContentAlignment="Stretch" ScrollViewer.PanningMode="VerticalFirst" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto">
    <ListBox.Resources>
        <DataTemplate x:Key="ItemLoadingContentTemplate">
            <GroupBox Header="{Binding}" Background="{DynamicResource MaterialDesign.Brush.Background}" Height="78" Padding="0">
                <GroupBox.Style>
                    <Style TargetType="GroupBox" BasedOn="{StaticResource MaterialDesignCardGroupBox}">
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding IsFocused}" Value="true">
                                <Setter Property="md:ColorZoneAssist.Mode" Value="PrimaryLight" />
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </GroupBox.Style>
                <GroupBox.HeaderTemplate>
                    <DataTemplate>
						<TextBlock Style="{StaticResource MaterialDesignSubtitle2TextBlock}" Text="{Binding Name}" />
                    </DataTemplate>
                </GroupBox.HeaderTemplate>
            </GroupBox>
        </DataTemplate>
        <DataTemplate x:Key="ItemLoadedContentTemplate">
            <v:AvailableProjectView DataContext="{Binding}" />
        </DataTemplate>
    </ListBox.Resources>
    <ListBox.ItemContainerStyle>
        <Style TargetType="ListBoxItem" BasedOn="{StaticResource MaterialDesignListBoxItem}">
            <Setter Property="Margin" Value="0" />
            <Setter Property="Padding" Value="0" />
        </Style>
    </ListBox.ItemContainerStyle>
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel Margin="4" />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    <ListBox.ItemTemplate>
        <DataTemplate>
            <c:LazyLoadContentControl Style="{StaticResource StyleLazyLoadContentControl}"
                                      DataContext="{Binding}" Content="{Binding}"
                                      LoadingContentTemplate="{StaticResource ItemLoadingContentTemplate}" LoadedContentTemplate="{StaticResource ItemLoadedContentTemplate}" />
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Error cause
As for what I found on this topic is that in some cases (e.g. when Virtualization is enabled on a ListBox) the items are temporarily removed from the Visual Tree but still kept alive because of the VirtualizationMode.Recycling. Since it has no parent anymore it will not have an Ancestor of type FrameworkElement an therefore resulting in the given binding error.

Fix proposal
What I found to prevent this is to define a FallbackValue. So in this case the binding in MaterialDesignCardGroupBox would change from:

<Setter Property="Foreground" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type FrameworkElement}}, Path=(TextElement.Foreground)}" />

to:

<Setter Property="Foreground" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type FrameworkElement}}, Path=(TextElement.Foreground), FallbackValue=Black}" />

Of course this fix needs to be applied in other styles using RelativeSource and AncestorType as well.

Wrap up
Please let me know what you think of defining a FallbackValue for the cases where we do a RelativeSource lookup by defining an AncestorType. If this is an acceptable solution I will create a PR that implements this in the places necessary.

Furthermore if you have any other toughs on the issue please let me know.

Version

Master branch

@JorisCleVR JorisCleVR added bug evaluation required Items is pending review or evaluation by the team labels May 28, 2025
@Keboo Keboo self-assigned this May 30, 2025
@Keboo
Copy link
Member

Keboo commented May 30, 2025

Hi @JorisCleVR I am perfectly fine with the solution of adding a fallback value. I will admit there are probably some other styles that could benefit from a similar change as well.

@Keboo Keboo added up-for-grabs and removed evaluation required Items is pending review or evaluation by the team labels May 30, 2025
@JorisCleVR
Copy link
Contributor Author

Oke, then I will go through the styles and check where a fallback value can be applied. I will get back to you when I have a PR available for it.

@JorisCleVR
Copy link
Contributor Author

Just now created a pull request with changes for all bindings that use AncestorType for the look up: #3862

@JorisCleVR
Copy link
Contributor Author

When looking further into the issues we are having with these bindings I found a more conclusive answer to why it goes wrong inside of a DataTemplate and wanted to place it here for completeness.
The reason of the error is that the ancestor lookup stops at a ContentPresenter (that is used to depict a DataTemplate). This means that the Ancestor lookup for a FrameworkElement of a in the examples case a GroupBox fails because the GroupBox is the root element of the DataTemplate.
To solve this issue an element that does an Ancestor look up should not be placed in the root of a DataTemplate. Nesting it inside of a FrameworkElement (e.g. Grid, StackPanel, DockPanel, Decorator) resolves this.
Note that placing it in a ContentControl does not resolve it, because then again it is depicted within the ContentPresenter of the ContentControl.

For my own code I created a DataTemplateContainer class (inheriting from Decorator) that is always the root element of my DataTemplates. This resolves the issue in all cases because now the GroupBox finds the DataTemplateContainer as the ancestor.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants