Arli  Chokoev
Arli Chokoev

Reputation: 549

ContentControl in a UserControl causes an exception

Adding a ContentControl with a binding into my UserControl causes an exception:

InvalidOperationException: Layout recursion reached allowed limit to avoid stack overflow: '2047'. Either the tree contains a loop or is too deep.

I try to switch between two views with use of content control:

    <ContentControl Grid.Row="2" Content="{Binding}">
        <ContentControl.Style>
            <Style TargetType="ContentControl">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding IsListView}" Value="True">
                        <Setter Property="ContentTemplate" Value="{DynamicResource ListTemplate}" />
                    </DataTrigger>
                    <DataTrigger Binding="{Binding IsTabView}" Value="True">
                        <Setter Property="ContentTemplate" Value="{DynamicResource TabTemplate}" />
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </ContentControl.Style>
    </ContentControl>

Once the Content={Binding} is removed, the exception is gone. How can I trace the source of the exception? Investigation of the exception members didn't give any clue on what can cause this recursion.

UPD: Leaving the ContentControl empty (e.g. without ListTemplate and TabTemplate) doesn't solve the problem.

Upvotes: 0

Views: 458

Answers (1)

Keith Stein
Keith Stein

Reputation: 6754

The Problem

ContentControl will attempt to show whatever the value of it's Content property is using a ContentPresenter. The MSDN doc I've linked describes the full logic that the ContentPresenter uses in determining how to best display the content it's been given, but these are the two most relevant points:

First it will try this:

  • If the ContentTemplate property on the ContentPresenter is set, the ContentPresenter applies that DataTemplate to the Content property and the resulting UIElement and its child elements, if any, are displayed.

Later on, if the above has failed (meaning ContentTemplate was not set), it will try this:

  • If Content is a UIElement object, the UIElement is displayed. If the UIElement already has a parent, an exception occurs.

When you first load the ContentControl, Content="{Binding}" sets Content to DataContext, which I'm assuming is your UserControl. Your Style has triggers that set ContentTemplate conditionally. This means there will be some moments when ContentTemplate is not set, so ContentPresenter will default to trying to display Content, which is a UIElement, as one of it's children.

So the ContentPresenter will try to display the entire UserControl as it's child, no problem- except part of that new "child" is itself, so to render than inner version of itself, it needs to display the Content property. And it continues like this in an infinite, recursive loop of ContentControls trying to display themselves within themselves until a limit is reached and WPF bails and throws an exception.

The Solution

  1. Always have a ContentTemplate.
    You'll need to have some sort of default ContentTemplate for the ContentControl to show if none of the triggers activate.

  2. Use ContentPresenter directly instead of ContentControl
    I'm not actually sure why this the case, but even after providing a default ContentTemplate, it still wouldn't work. It probably has something to do with the internal workings of ContentControl. However, switching out the ContentControl for a ContentPresenter fixes everything.

<ContentPresenter Grid.Row="2" Content="{Binding}">
    <ContentPresenter.Style>
        <Style TargetType="ContentPresenter">
            <Setter Property="ContentTemplate">
                <Setter.Value>
                    <!--"Default" template that will be used if none of the triggers are active-->
                    <DataTemplate>
                    </DataTemplate>
                </Setter.Value>
            </Setter>
                    
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsListView}" Value="True">
                    <Setter Property="ContentTemplate" Value="{DynamicResource ListTemplate}" />
                </DataTrigger>
                <DataTrigger Binding="{Binding IsTabView}" Value="True">
                    <Setter Property="ContentTemplate" Value="{DynamicResource TabTemplate}" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </ContentPresenter.Style>
</ContentPresenter>

Upvotes: 4

Related Questions