diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs
index 40fc2f302c..07a087365d 100644
--- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs
@@ -8,6 +8,7 @@ using Avalonia.Controls.Templates;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Media;
+using Avalonia.VisualTree;
namespace Avalonia.Controls.Presenters
{
@@ -313,27 +314,22 @@ namespace Avalonia.Controls.Presenters
if (content != null && newChild == null)
{
- // We have content and it isn't a control, so first try to recycle the existing
- // child control to display the new data by querying if the template that created
- // the child can recycle items and that it also matches the new data.
- if (oldChild != null &&
- _dataTemplate != null &&
- _dataTemplate.SupportsRecycling &&
- _dataTemplate.Match(content))
+ var dataTemplate = this.FindDataTemplate(content, ContentTemplate) ?? FuncDataTemplate.Default;
+
+ // We have content and it isn't a control, so if the new data template is the same
+ // as the old data template, try to recycle the existing child control to display
+ // the new data.
+ if (dataTemplate == _dataTemplate && dataTemplate.SupportsRecycling)
{
newChild = oldChild;
}
else
{
- // We couldn't recycle an existing control so find a data template for the data
- // and use it to create a control.
- _dataTemplate = this.FindDataTemplate(content, ContentTemplate) ?? FuncDataTemplate.Default;
+ _dataTemplate = dataTemplate;
newChild = _dataTemplate.Build(content);
- // Try to give the new control its own name scope.
- var controlResult = newChild as Control;
-
- if (controlResult != null)
+ // Give the new control its own name scope.
+ if (newChild is Control controlResult)
{
NameScope.SetNameScope(controlResult, new NameScope());
}
@@ -424,6 +420,19 @@ namespace Avalonia.Controls.Presenters
private void ContentChanged(AvaloniaPropertyChangedEventArgs e)
{
_createdChild = false;
+
+ if (((ILogical)this).IsAttachedToLogicalTree)
+ {
+ UpdateChild();
+ }
+ else if (Child != null)
+ {
+ VisualChildren.Remove(Child);
+ LogicalChildren.Remove(Child);
+ Child = null;
+ _dataTemplate = null;
+ }
+
InvalidateMeasure();
}
diff --git a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj
index f7b63cdb75..957cdd7036 100644
--- a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj
+++ b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj
@@ -8,6 +8,9 @@
+
+
+
diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs
new file mode 100644
index 0000000000..9ea03587ed
--- /dev/null
+++ b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs
@@ -0,0 +1,265 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System.Linq;
+using Avalonia.Controls.Presenters;
+using Avalonia.Controls.Templates;
+using Avalonia.LogicalTree;
+using Avalonia.UnitTests;
+using Avalonia.VisualTree;
+using Moq;
+using Xunit;
+
+namespace Avalonia.Controls.UnitTests.Presenters
+{
+ ///
+ /// Tests for ContentControls that are hosted in a control template.
+ ///
+ public class ContentPresenterTests_InTemplate
+ {
+ [Fact]
+ public void Should_Register_With_Host_When_TemplatedParent_Set()
+ {
+ var host = new Mock();
+ var target = new ContentPresenter();
+
+ target.SetValue(Control.TemplatedParentProperty, host.Object);
+
+ host.Verify(x => x.RegisterContentPresenter(target));
+ }
+
+ [Fact]
+ public void Setting_Content_To_Control_Should_Set_Child()
+ {
+ var (target, _) = CreateTarget();
+ var child = new Border();
+
+ target.Content = child;
+
+ Assert.Equal(child, target.Child);
+ }
+
+ [Fact]
+ public void Setting_Content_To_Control_Should_Update_Logical_Tree()
+ {
+ var (target, parent) = CreateTarget();
+ var child = new Border();
+
+ target.Content = child;
+
+ Assert.Equal(parent, child.GetLogicalParent());
+ Assert.Equal(new[] { child }, parent.GetLogicalChildren());
+ }
+
+ [Fact]
+ public void Setting_Content_To_Control_Should_Update_Visual_Tree()
+ {
+ var (target, _) = CreateTarget();
+ var child = new Border();
+
+ target.Content = child;
+
+ Assert.Equal(target, child.GetVisualParent());
+ Assert.Equal(new[] { child }, target.GetVisualChildren());
+ }
+
+ [Fact]
+ public void Setting_Content_To_String_Should_Create_TextBlock()
+ {
+ var (target, _) = CreateTarget();
+
+ target.Content = "Foo";
+
+ Assert.IsType(target.Child);
+ Assert.Equal("Foo", ((TextBlock)target.Child).Text);
+ }
+
+ [Fact]
+ public void Setting_Content_To_String_Should_Update_Logical_Tree()
+ {
+ var (target, parent) = CreateTarget();
+
+ target.Content = "Foo";
+
+ var child = target.Child;
+ Assert.Equal(parent, child.GetLogicalParent());
+ Assert.Equal(new[] { child }, parent.GetLogicalChildren());
+ }
+
+ [Fact]
+ public void Setting_Content_To_String_Should_Update_Visual_Tree()
+ {
+ var (target, _) = CreateTarget();
+
+ target.Content = "Foo";
+
+ var child = target.Child;
+ Assert.Equal(target, child.GetVisualParent());
+ Assert.Equal(new[] { child }, target.GetVisualChildren());
+ }
+
+ [Fact]
+ public void Clearing_Control_Content_Should_Update_Logical_Tree()
+ {
+ var (target, _) = CreateTarget();
+ var child = new Border();
+
+ target.Content = child;
+ target.Content = null;
+
+ Assert.Equal(null, child.GetLogicalParent());
+ Assert.Empty(target.GetLogicalChildren());
+ }
+
+ [Fact]
+ public void Clearing_Control_Content_Should_Update_Visual_Tree()
+ {
+ var (target, _) = CreateTarget();
+ var child = new Border();
+
+ target.Content = child;
+ target.Content = null;
+
+ Assert.Equal(null, child.GetVisualParent());
+ Assert.Empty(target.GetVisualChildren());
+ }
+
+ [Fact]
+ public void Control_Content_Should_Not_Be_NameScope()
+ {
+ var (target, _) = CreateTarget();
+
+ target.Content = new TextBlock();
+
+ Assert.IsType(target.Child);
+ Assert.Null(NameScope.GetNameScope((Control)target.Child));
+ }
+
+ [Fact]
+ public void DataTemplate_Created_Control_Should_Be_NameScope()
+ {
+ var (target, _) = CreateTarget();
+
+ target.Content = "Foo";
+
+ Assert.IsType(target.Child);
+ Assert.NotNull(NameScope.GetNameScope((Control)target.Child));
+ }
+
+ [Fact]
+ public void Assigning_Control_To_Content_Should_Not_Set_DataContext()
+ {
+ var (target, _) = CreateTarget();
+ target.Content = new Border();
+
+ Assert.False(target.IsSet(Control.DataContextProperty));
+ }
+
+ [Fact]
+ public void Assigning_NonControl_To_Content_Should_Set_DataContext_On_UpdateChild()
+ {
+ var (target, _) = CreateTarget();
+ target.Content = "foo";
+
+ Assert.Equal("foo", target.DataContext);
+ }
+
+ [Fact]
+ public void Assigning_Control_To_Content_After_NonControl_Should_Clear_DataContext()
+ {
+ var (target, _) = CreateTarget();
+
+ target.Content = "foo";
+
+ Assert.True(target.IsSet(Control.DataContextProperty));
+
+ target.Content = new Border();
+
+ Assert.False(target.IsSet(Control.DataContextProperty));
+ }
+
+ [Fact]
+ public void Recycles_DataTemplate()
+ {
+ var (target, _) = CreateTarget();
+ target.DataTemplates.Add(new FuncDataTemplate(_ => new Border(), true));
+
+ target.Content = "foo";
+
+ var control = target.Child;
+ Assert.IsType(control);
+
+ target.Content = "bar";
+ Assert.Same(control, target.Child);
+ }
+
+ [Fact]
+ public void Detects_DataTemplate_Doesnt_Match_And_Doesnt_Recycle()
+ {
+ var (target, _) = CreateTarget();
+ target.DataTemplates.Add(new FuncDataTemplate(x => x == "foo", _ => new Border(), true));
+
+ target.Content = "foo";
+
+ var control = target.Child;
+ Assert.IsType(control);
+
+ target.Content = "bar";
+ Assert.IsType(target.Child);
+ }
+
+ [Fact]
+ public void Detects_DataTemplate_Doesnt_Support_Recycling()
+ {
+ var (target, _) = CreateTarget();
+ target.DataTemplates.Add(new FuncDataTemplate(_ => new Border(), false));
+
+ target.Content = "foo";
+
+ var control = target.Child;
+ Assert.IsType(control);
+
+ target.Content = "bar";
+ Assert.NotSame(control, target.Child);
+ }
+
+ [Fact]
+ public void Reevaluates_DataTemplates_When_Recycling()
+ {
+ var (target, _) = CreateTarget();
+
+ target.DataTemplates.Add(new FuncDataTemplate(x => x == "bar", _ => new Canvas(), true));
+ target.DataTemplates.Add(new FuncDataTemplate(_ => new Border(), true));
+
+ target.Content = "foo";
+
+ var control = target.Child;
+ Assert.IsType(control);
+
+ target.Content = "bar";
+ Assert.IsType