diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs index c1adff402a..97760b5632 100644 --- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs @@ -255,18 +255,9 @@ namespace Avalonia.Controls.Presenters LogicalChildren.Remove(oldChild); } - if (newChild.Parent == null) + if (newChild.Parent == null && TemplatedParent == null) { - var templatedLogicalParent = TemplatedParent as ILogical; - - if (templatedLogicalParent != null) - { - ((ISetLogicalParent)newChild).SetParent(templatedLogicalParent); - } - else - { - LogicalChildren.Add(newChild); - } + LogicalChildren.Add(newChild); } VisualChildren.Add(newChild); diff --git a/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs b/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs index 5f86b9878d..653ab17b63 100644 --- a/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs @@ -1,6 +1,7 @@ // 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; using System.Collections.Specialized; using System.Linq; using Moq; @@ -11,6 +12,9 @@ using Avalonia.Styling; using Avalonia.UnitTests; using Avalonia.VisualTree; using Xunit; +using Avalonia.Markup.Xaml.Data; +using Avalonia.Data; +using System.Collections.Generic; namespace Avalonia.Controls.UnitTests { @@ -273,6 +277,60 @@ namespace Avalonia.Controls.UnitTests Assert.Null(target.Presenter.Child.DataContext); } + [Fact] + public void Binding_ContentTemplate_After_Content_Does_Not_Leave_Orpaned_TextBlock() + { + // Test for #1271. + var children = new List(); + var presenter = new ContentPresenter(); + + // The content and then the content template property need to be bound with delayed bindings + // as they are in Avalonia.Markup.Xaml. + DelayedBinding.Add(presenter, ContentPresenter.ContentProperty, new Binding("Content") + { + Priority = BindingPriority.TemplatedParent, + RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), + }); + + DelayedBinding.Add(presenter, ContentPresenter.ContentTemplateProperty, new Binding("ContentTemplate") + { + Priority = BindingPriority.TemplatedParent, + RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), + }); + + presenter.GetObservable(ContentPresenter.ChildProperty).Subscribe(children.Add); + + var target = new ContentControl + { + Template = new FuncControlTemplate(_ => presenter), + ContentTemplate = new FuncDataTemplate(x => new Canvas()), + Content = "foo", + }; + + // The control must be rooted. + var root = new TestRoot + { + Child = target, + }; + + target.ApplyTemplate(); + + // When the template is applied, the Content property is bound before the ContentTemplate + // property, causing a TextBlock to be created by the default template before ContentTemplate + // is bound. + Assert.Collection( + children, + x => Assert.Null(x), + x => Assert.IsType(x), + x => Assert.IsType(x)); + + var textBlock = (TextBlock)children[1]; + + // The leak in #1271 was caused by the TextBlock's logical parent not being cleared when + // it is replaced by the Canvas. + Assert.Null(textBlock.GetLogicalParent()); + } + private FuncControlTemplate GetTemplate() { return new FuncControlTemplate(parent =>