Skip to content

ContentControl doesn't update ControlTemplate when moved to another container (parent changes) #4660

@marbel82

Description

@marbel82
  • .NET Core Version: 5.0.301
  • Windows version: 10 (20H2)
  • Does the bug reproduce also in WPF for .NET Framework 4.8?: Yes

Problem description:
ContentControl doesn't update ControlTemplate when moved to another container (parent changes). Is it a bug or the correct action?

Minimal repro:

I have two StackPanels. Each of them has defined a DataTemplate of the same LineItem type. When I move ContentControl from one to the other container I suspect that the view of the items will change. But it doesn't happen.

Create "WPF App (.NET 5)" or "WPF App (.NET Framework)" project and override MainWindow files:

<Window x:Class="WpfApp7core.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp7core"
        mc:Ignorable="d"
        Title="MainWindow" Height="300" Width="600">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <TextBlock Text="Column 1" HorizontalAlignment="Center"/>
        <TextBlock Text="Column 2" HorizontalAlignment="Center" Grid.Column="1"/>

        <StackPanel x:Name="column1" Grid.Column="0" Grid.Row="1">
            <StackPanel.Resources>
                <DataTemplate DataType="{x:Type local:LineItem}">
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Name}" Margin="4,0"/>
                        <TextBlock Text="{Binding Value}" Foreground="Blue"/>
                    </StackPanel>
                </DataTemplate>
            </StackPanel.Resources>
            <!-- The place where new ContentControls are added.  -->
        </StackPanel>

        <StackPanel x:Name="column2" Grid.Column="1" Grid.Row="1">
            <StackPanel.Resources>
                <DataTemplate DataType="{x:Type local:LineItem}">
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Name}" FontWeight="Bold" Margin="4,0"/>
                        <TextBlock Text="{Binding Value}" Foreground="Green"/>
                    </StackPanel>
                </DataTemplate>
            </StackPanel.Resources>
            <!-- The place where new ContentControls are added.  -->
        </StackPanel>

        <StackPanel Orientation="Horizontal" Grid.Row="2" Grid.ColumnSpan="2">
            <Button Content=" Create in 1 " Click="Create1_Click"/>
            <Button Content=" Create in 2 " Click="Create2_Click"/>
            <Button Content=" Move to > " Click="MoveTo2_Click"/>
            <Button Content=" Move to &lt; " Click="MoveTo1_Click"/>
        </StackPanel>
    </Grid>

</Window>
namespace WpfApp7core
{
    public class LineItem
    {
        public string Name { get; set; }
        public string Value { get; set; }
    }

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Create1_Click(object sender, RoutedEventArgs e)
        {
            var li = new LineItem { Name = "Left", Value = Guid.NewGuid().ToString() };
            column1.Children.Add(new ContentControl { Content = li });
        }

        private void Create2_Click(object sender, RoutedEventArgs e)
        {
            var li = new LineItem { Name = "Right", Value = Guid.NewGuid().ToString() };
            column2.Children.Add(new ContentControl { Content = li });
        }

        private void MoveTo2_Click(object sender, RoutedEventArgs e)
        {
            foreach (FrameworkElement item in Enumerable.Range(0, column1.Children.Count).Select(i => column1.Children[i]).Cast<FrameworkElement>().ToList())
            {
                RemoveFromParent(item); // Remove from column1
                column2.Children.Add(item); // Add to column2
            }
        }

        private void MoveTo1_Click(object sender, RoutedEventArgs e)
        {
            foreach (FrameworkElement item in Enumerable.Range(0, column2.Children.Count).Select(i => column2.Children[i]).Cast<FrameworkElement>().ToList())
            {
                RemoveFromParent(item); // Remove from column2
                column1.Children.Add(item); // Add to column1
            }
        }

        public static void RemoveFromParent(FrameworkElement child)
        {
            switch (child.Parent)
            {
                case Panel panel: panel.Children.Remove(child); break;
                case Decorator decorator: decorator.Child = null; break;
                case ContentControl contentControl: contentControl.Content = null; break;
                case ContentPresenter contentPresenter: contentPresenter.Content = null; break;
                default: throw new Exception($"Unknown parent type {child.Parent.GetType()}");
            }
        }
    }
}

MovingContentControlBug

Expected behaviour:
Each items on the left should be blue, and each on the right should be green.

Unwanted solution:
I found one solution. When I assign Content to null and then assign the previous value, ContentControl will update its view.

            foreach (FrameworkElement item in Enumerable.Range(0, column1.Children.Count).Select(i => column1.Children[i]).Cast<FrameworkElement>().ToList())
            {
                RemoveFromParent(item);
                column2.Children.Add(item);

                if (item is ContentControl cc)
                {
                    var prev = cc.Content;
                    cc.Content = null;
                    cc.Content = prev;
                }
            }

But this solution becomes complicated when you have an extensive view tree.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions