Browse Source

Don't reapply templates unnecessarily.

When detaching and re-attaching to the same logical tree.
Steven Kirk 9 years ago
parent
commit
72cb1cf311

+ 14 - 11
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@@ -94,7 +94,7 @@ namespace Avalonia.Controls.Primitives
                 "TemplateApplied", 
                 RoutingStrategies.Direct);
 
-        private bool _templateApplied;
+        private IControlTemplate _appliedTemplate;
 
         /// <summary>
         /// Initializes static members of the <see cref="TemplatedControl"/> class.
@@ -234,7 +234,16 @@ namespace Avalonia.Controls.Primitives
         /// <inheritdoc/>
         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
         /// <param name="e">The event args.</param>
         protected virtual void OnTemplateChanged(AvaloniaPropertyChangedEventArgs e)
         {
-            if (_templateApplied && VisualChildren.Count > 0)
-            {
-                _templateApplied = false;
-            }
-
-            _templateApplied = false;
             InvalidateMeasure();
         }
 

+ 137 - 0
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<TestTemplatedControl>())
+                        {
+                            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<Decorator>(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<TestTemplatedControl>())
+                        {
+                            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<TestTemplatedControl>())
+                        {
+                            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<TestTemplatedControl>())
+                        {
+                            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