Browse Source

Merge pull request #568 from donandren/contentpresenterbug

Contentpresenter bug
Steven Kirk 9 years ago
parent
commit
e4e60fb73a

+ 15 - 1
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@@ -277,9 +277,23 @@ namespace Avalonia.Controls.Presenters
 
                 Child = newChild;
 
+                if (oldChild?.LogicalParent == this)
+                {
+                    LogicalChildren.Remove(oldChild);
+                }
+
                 if (newChild.Parent == null)
                 {
-                    ((ISetLogicalParent)newChild).SetParent((ILogical)this.TemplatedParent ?? this);
+                    var templatedLogicalParent = TemplatedParent as ILogical;
+
+                    if (templatedLogicalParent != null)
+                    {
+                        ((ISetLogicalParent)newChild).SetParent(templatedLogicalParent);
+                    }
+                    else
+                    {
+                        LogicalChildren.Add(newChild);
+                    }
                 }
 
                 VisualChildren.Add(newChild);

+ 173 - 4
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests.cs

@@ -1,15 +1,17 @@
 // 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 Moq;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Templates;
+using Avalonia.LogicalTree;
+using Avalonia.Styling;
 using Avalonia.UnitTests;
 using Avalonia.VisualTree;
-using Xunit;
+using Moq;
 using System;
+using System.Linq;
+using Xunit;
 
 namespace Avalonia.Controls.UnitTests.Presenters
 {
@@ -107,6 +109,20 @@ namespace Avalonia.Controls.UnitTests.Presenters
             Assert.Same(target, content.Parent);
         }
 
+        [Fact]
+        public void Should_Add_Child_To_Own_LogicalChildren_Outside_Template()
+        {
+            var content = new Border();
+            var target = new ContentPresenter { Content = content };
+
+            target.UpdateChild();
+
+            var logicalChildren = target.GetLogicalChildren();
+
+            Assert.Equal(1, logicalChildren.Count());
+            Assert.Equal(content, logicalChildren.First());
+        }
+
         [Fact]
         public void Adding_To_Logical_Tree_Should_Reevaluate_DataTemplates()
         {
@@ -178,9 +194,162 @@ namespace Avalonia.Controls.UnitTests.Presenters
             Assert.Same(control, target.Child);
         }
 
+        [Fact]
+        public void Should_Raise_DetachedFromLogicalTree_On_Content_Changed_OutsideTemplate()
+        {
+            var target = new ContentPresenter
+            {
+                ContentTemplate =
+                    new FuncDataTemplate<string>(t => new ContentControl() { Content = t }, false)
+            };
+
+            var parentMock = new Mock<Control>();
+            parentMock.As<IContentPresenterHost>();
+            parentMock.As<IStyleRoot>();
+
+            (target as ISetLogicalParent).SetParent(parentMock.Object);
+
+            target.Content = "foo";
+
+            target.UpdateChild();
+
+            var foo = target.Child as ContentControl;
+
+            bool foodetached = false;
+
+            Assert.NotNull(foo);
+            Assert.Equal("foo", foo.Content);
+
+            foo.DetachedFromLogicalTree += delegate { foodetached = true; };
+
+            target.Content = "bar";
+            target.UpdateChild();
+
+            var bar = target.Child as ContentControl;
+
+            Assert.NotNull(bar);
+            Assert.True(bar != foo);
+            Assert.False((foo as IControl).IsAttachedToLogicalTree);
+            Assert.True(foodetached);
+        }
+
+        [Fact]
+        public void Should_Raise_DetachedFromLogicalTree_In_ContentControl_On_Content_Changed_OutsideTemplate()
+        {
+            var contentControl = new ContentControl
+            {
+                Template = new FuncControlTemplate<ContentControl>(c => new ContentPresenter()
+                {
+                    Name = "PART_ContentPresenter",
+                    [~ContentPresenter.ContentProperty] = c[~ContentControl.ContentProperty],
+                    [~ContentPresenter.ContentTemplateProperty] = c[~ContentControl.ContentTemplateProperty]
+                }),
+                ContentTemplate =
+                    new FuncDataTemplate<string>(t => new ContentControl() { Content = t }, false)
+            };
+
+            var parentMock = new Mock<Control>();
+            parentMock.As<IStyleRoot>();
+            parentMock.As<ILogical>().SetupGet(l => l.IsAttachedToLogicalTree).Returns(true);
+
+            (contentControl as ISetLogicalParent).SetParent(parentMock.Object);
+
+            contentControl.ApplyTemplate();
+            var target = contentControl.Presenter as ContentPresenter;
+
+            contentControl.Content = "foo";
+
+            target.UpdateChild();
+
+            var tbfoo = target.Child as ContentControl;
+
+            bool foodetached = false;
+
+            Assert.NotNull(tbfoo);
+            Assert.Equal("foo", tbfoo.Content);
+
+            tbfoo.DetachedFromLogicalTree += delegate { foodetached = true; };
+
+            contentControl.Content = "bar";
+            target.UpdateChild();
+
+            var tbbar = target.Child as ContentControl;
+
+            Assert.NotNull(tbbar);
+            Assert.True(tbbar != tbfoo);
+            Assert.False((tbfoo as IControl).IsAttachedToLogicalTree);
+            Assert.True(foodetached);
+        }
+
+        [Fact]
+        public void Should_Raise_DetachedFromLogicalTree_On_Detached_OutsideTemplate()
+        {
+            var target = new ContentPresenter
+            {
+                ContentTemplate =
+                    new FuncDataTemplate<string>(t => new ContentControl() { Content = t }, false)
+            };
+
+            var parentMock = new Mock<Control>();
+            parentMock.As<IContentPresenterHost>();
+            parentMock.As<IStyleRoot>();
+
+            (target as ISetLogicalParent).SetParent(parentMock.Object);
+
+            target.Content = "foo";
+
+            target.UpdateChild();
+
+            var foo = target.Child as ContentControl;
+
+            bool foodetached = false;
+
+            Assert.NotNull(foo);
+            Assert.Equal("foo", foo.Content);
+
+            foo.DetachedFromLogicalTree += delegate { foodetached = true; };
+
+            (target as ISetLogicalParent).SetParent(null);
+
+            Assert.False((foo as IControl).IsAttachedToLogicalTree);
+            Assert.True(foodetached);
+        }
+
+        [Fact]
+        public void Should_Remove_Old_Child_From_LogicalChildren_On_ContentChanged_OutsideTemplate()
+        {
+            var target = new ContentPresenter
+            {
+                ContentTemplate =
+                    new FuncDataTemplate<string>(t => new ContentControl() { Content = t }, false)
+            };
+
+            target.Content = "foo";
+
+            target.UpdateChild();
+
+            var foo = target.Child as ContentControl;
+
+            Assert.NotNull(foo);
+
+            var logicalChildren = target.GetLogicalChildren();
+
+            Assert.Equal(1, logicalChildren.Count());
+
+            target.Content = "bar";
+            target.UpdateChild();
+
+            Assert.Equal(null, foo.Parent);
+
+            logicalChildren = target.GetLogicalChildren();
+
+            Assert.Equal(1, logicalChildren.Count());
+            Assert.NotEqual(foo, logicalChildren.First());
+        }
+
         private class TestContentControl : ContentControl
         {
             public IControl Child { get; set; }
         }
     }
-}
+}