diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index e073cf4996..d16f857f7c 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -94,7 +94,7 @@ namespace Avalonia.Controls.Primitives "TemplateApplied", RoutingStrategies.Direct); - private bool _templateApplied; + private IControlTemplate _appliedTemplate; /// /// Initializes static members of the class. @@ -234,7 +234,16 @@ namespace Avalonia.Controls.Primitives /// public sealed override void ApplyTemplate() { - if (!_templateApplied) + var template = Template; + var logical = (ILogical)this; + + // Apply the template if it is not the same as the template already applied - except + // for in the case that the template is null and we're not attached to the logical + // tree. In that case, the template has probably been cleared because the style setting + // the template has been detached, so we want to wait until it's re-attached to the + // logical tree as if it's re-attached to the same tree the template will be the same + // and we don't need to do anything. + if (_appliedTemplate != template && (template != null || logical.IsAttachedToLogicalTree)) { if (VisualChildren.Count > 0) { @@ -246,11 +255,11 @@ namespace Avalonia.Controls.Primitives VisualChildren.Clear(); } - if (Template != null) + if (template != null) { Logger.Verbose(LogArea.Control, this, "Creating control template"); - var child = Template.Build(this); + var child = template.Build(this); var nameScope = new NameScope(); NameScope.SetNameScope((Control)child, nameScope); child.SetValue(TemplatedParentProperty, this); @@ -261,7 +270,7 @@ namespace Avalonia.Controls.Primitives OnTemplateApplied(new TemplateAppliedEventArgs(nameScope)); } - _templateApplied = true; + _appliedTemplate = template; } } @@ -322,12 +331,6 @@ namespace Avalonia.Controls.Primitives /// The event args. protected virtual void OnTemplateChanged(AvaloniaPropertyChangedEventArgs e) { - if (_templateApplied && VisualChildren.Count > 0) - { - _templateApplied = false; - } - - _templateApplied = false; InvalidateMeasure(); } diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs index fb2434b74d..d8e43e4c37 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs @@ -390,6 +390,143 @@ namespace Avalonia.Controls.UnitTests.Primitives Assert.True(raised); } + [Fact] + public void Removing_From_LogicalTree_Should_Not_Remove_Child() + { + using (UnitTestApplication.Start(TestServices.RealStyler)) + { + Border templateChild = new Border(); + TestTemplatedControl target; + var root = new TestRoot + { + Styles = new Styles + { + new Style(x => x.OfType()) + { + Setters = new[] + { + new Setter( + TemplatedControl.TemplateProperty, + new FuncControlTemplate(_ => new Decorator + { + Child = new Border(), + })) + } + } + }, + Child = target = new TestTemplatedControl() + }; + + Assert.NotNull(target.Template); + target.ApplyTemplate(); + + root.Child = null; + + Assert.Null(target.Template); + Assert.IsType(target.GetVisualChildren().Single()); + } + } + + [Fact] + public void Re_adding_To_Same_LogicalTree_Should_Not_Recreate_Template() + { + using (UnitTestApplication.Start(TestServices.RealStyler)) + { + TestTemplatedControl target; + var root = new TestRoot + { + Styles = new Styles + { + new Style(x => x.OfType()) + { + Setters = new[] + { + new Setter( + TemplatedControl.TemplateProperty, + new FuncControlTemplate(_ => new Decorator + { + Child = new Border(), + })) + } + } + }, + Child = target = new TestTemplatedControl() + }; + + Assert.NotNull(target.Template); + target.ApplyTemplate(); + var expected = (Decorator)target.GetVisualChildren().Single(); + + root.Child = null; + root.Child = target; + target.ApplyTemplate(); + + Assert.Same(expected, target.GetVisualChildren().Single()); + } + } + + [Fact] + public void Re_adding_To_Different_LogicalTree_Should_Recreate_Template() + { + using (UnitTestApplication.Start(TestServices.RealStyler)) + { + TestTemplatedControl target; + + var root = new TestRoot + { + Styles = new Styles + { + new Style(x => x.OfType()) + { + Setters = new[] + { + new Setter( + TemplatedControl.TemplateProperty, + new FuncControlTemplate(_ => new Decorator + { + Child = new Border(), + })) + } + } + }, + Child = target = new TestTemplatedControl() + }; + + var root2 = new TestRoot + { + Styles = new Styles + { + new Style(x => x.OfType()) + { + Setters = new[] + { + new Setter( + TemplatedControl.TemplateProperty, + new FuncControlTemplate(_ => new Decorator + { + Child = new Border(), + })) + } + } + }, + }; + + Assert.NotNull(target.Template); + target.ApplyTemplate(); + + var expected = (Decorator)target.GetVisualChildren().Single(); + + root.Child = null; + root2.Child = target; + target.ApplyTemplate(); + + var child = target.GetVisualChildren().Single(); + Assert.NotNull(target.Template); + Assert.NotNull(child); + Assert.NotSame(expected, child); + } + } + private static IControl ScrollingContentControlTemplate(ContentControl control) { return new Border