Răsfoiți Sursa

Merge pull request #2926 from AvaloniaUI/fixes/2821-remove-contentcontrolmixin

Remove ContentControlMixin
danwalmsley 6 ani în urmă
părinte
comite
e41e38249b

+ 25 - 7
src/Avalonia.Controls/ContentControl.cs

@@ -1,11 +1,13 @@
 // 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 Avalonia.Collections;
 using Avalonia.Controls.Mixins;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Templates;
 using Avalonia.Layout;
+using Avalonia.LogicalTree;
 using Avalonia.Metadata;
 
 namespace Avalonia.Controls
@@ -39,12 +41,9 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty =
             AvaloniaProperty.Register<ContentControl, VerticalAlignment>(nameof(VerticalContentAlignment));
 
-        /// <summary>
-        /// Initializes static members of the <see cref="ContentControl"/> class.
-        /// </summary>
         static ContentControl()
         {
-            ContentControlMixin.Attach<ContentControl>(ContentProperty, x => x.LogicalChildren);
+            ContentProperty.Changed.AddClassHandler<ContentControl>(x => x.ContentChanged);
         }
 
         /// <summary>
@@ -95,20 +94,39 @@ namespace Avalonia.Controls
         }
 
         /// <inheritdoc/>
-        void IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter)
+        IAvaloniaList<ILogical> IContentPresenterHost.LogicalChildren => LogicalChildren;
+
+        /// <inheritdoc/>
+        bool IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter)
         {
-            RegisterContentPresenter(presenter);
+            return RegisterContentPresenter(presenter);
         }
 
         /// <summary>
         /// Called when an <see cref="IContentPresenter"/> is registered with the control.
         /// </summary>
         /// <param name="presenter">The presenter.</param>
-        protected virtual void RegisterContentPresenter(IContentPresenter presenter)
+        protected virtual bool RegisterContentPresenter(IContentPresenter presenter)
         {
             if (presenter.Name == "PART_ContentPresenter")
             {
                 Presenter = presenter;
+                return true;
+            }
+
+            return false;
+        }
+
+        private void ContentChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (e.OldValue is ILogical oldChild)
+            {
+                LogicalChildren.Remove(oldChild);
+            }
+
+            if (e.NewValue is ILogical newChild)
+            {
+                LogicalChildren.Add(newChild);
             }
         }
     }

+ 0 - 166
src/Avalonia.Controls/Mixins/ContentControlMixin.cs

@@ -1,166 +0,0 @@
-// 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.Linq;
-using System.Reactive.Disposables;
-using System.Runtime.CompilerServices;
-using Avalonia.Collections;
-using Avalonia.Controls.Presenters;
-using Avalonia.Controls.Primitives;
-using Avalonia.Interactivity;
-using Avalonia.LogicalTree;
-
-namespace Avalonia.Controls.Mixins
-{
-    /// <summary>
-    /// Adds content control functionality to control classes.
-    /// </summary>
-    /// <para>
-    /// The <see cref="ContentControlMixin"/> adds behavior to a control which acts as a content
-    /// control such as <see cref="ContentControl"/> and <see cref="HeaderedItemsControl"/>. It
-    /// keeps the control's logical children in sync with the content being displayed by the
-    /// control.
-    /// </para>
-    public class ContentControlMixin
-    {
-        private static Lazy<ConditionalWeakTable<TemplatedControl, IDisposable>> subscriptions = 
-            new Lazy<ConditionalWeakTable<TemplatedControl, IDisposable>>(() => 
-                new ConditionalWeakTable<TemplatedControl, IDisposable>());
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="SelectableMixin"/> class.
-        /// </summary>
-        /// <typeparam name="TControl">The control type.</typeparam>
-        /// <param name="content">The content property.</param>
-        /// <param name="logicalChildrenSelector">
-        /// Given an control of <typeparamref name="TControl"/> should return the control's
-        /// logical children collection.
-        /// </param>
-        /// <param name="presenterName">
-        /// The name of the content presenter in the control's template.
-        /// </param>
-        public static void Attach<TControl>(
-            AvaloniaProperty content,            
-            Func<TControl, IAvaloniaList<ILogical>> logicalChildrenSelector,
-            string presenterName = "PART_ContentPresenter")
-            where TControl : TemplatedControl
-        {
-            Contract.Requires<ArgumentNullException>(content != null);
-            Contract.Requires<ArgumentNullException>(logicalChildrenSelector != null);
-
-            void ChildChanging(object s, AvaloniaPropertyChangedEventArgs e)
-            {
-                if (s is IControl sender && sender?.TemplatedParent is TControl parent)
-                {
-                    UpdateLogicalChild(
-                        sender,
-                        logicalChildrenSelector(parent),
-                        e.OldValue,
-                        null);
-                }
-            }
-
-            void TemplateApplied(object s, RoutedEventArgs ev)
-            {
-                if (s is TControl sender)
-                {
-                    var e = (TemplateAppliedEventArgs)ev;
-                    var presenter = e.NameScope.Find(presenterName) as IContentPresenter;
-
-                    if (presenter != null)
-                    {
-                        presenter.ApplyTemplate();
-
-                        var logicalChildren = logicalChildrenSelector(sender);
-                        var subscription = new CompositeDisposable();
-
-                        presenter.ChildChanging += ChildChanging;
-                        subscription.Add(Disposable.Create(() => presenter.ChildChanging -= ChildChanging));
-
-                        subscription.Add(presenter
-                            .GetPropertyChangedObservable(ContentPresenter.ChildProperty)
-                            .Subscribe(c => UpdateLogicalChild(
-                                sender,
-                                logicalChildren,
-                                null,
-                                c.NewValue)));
-
-                        UpdateLogicalChild(
-                            sender,
-                            logicalChildren,
-                            null,
-                            presenter.GetValue(ContentPresenter.ChildProperty));
-
-                        if (subscriptions.Value.TryGetValue(sender, out IDisposable previousSubscription))
-                        {
-                            subscription = new CompositeDisposable(previousSubscription, subscription);
-                            subscriptions.Value.Remove(sender);
-                        }
-
-                        subscriptions.Value.Add(sender, subscription);
-                    }
-                }
-            }
-
-            TemplatedControl.TemplateAppliedEvent.AddClassHandler(
-                typeof(TControl),
-                TemplateApplied,
-                RoutingStrategies.Direct);
-
-            content.Changed.Subscribe(e =>
-            {
-                if (e.Sender is TControl sender)
-                {
-                    var logicalChildren = logicalChildrenSelector(sender);
-                    UpdateLogicalChild(sender, logicalChildren, e.OldValue, e.NewValue);
-                }
-            });
-
-            Control.TemplatedParentProperty.Changed.Subscribe(e =>
-            {
-                if (e.Sender is TControl sender)
-                {
-                    var logicalChild = logicalChildrenSelector(sender).FirstOrDefault() as IControl;
-                    logicalChild?.SetValue(Control.TemplatedParentProperty, sender.TemplatedParent);
-                }
-            });
-
-            TemplatedControl.TemplateProperty.Changed.Subscribe(e =>
-            {
-                if (e.Sender is TControl sender)
-                {
-                    if (subscriptions.Value.TryGetValue(sender, out IDisposable subscription))
-                    {
-                        subscription.Dispose();
-                        subscriptions.Value.Remove(sender);
-                    }
-                }
-            });
-        }
-
-        private static void UpdateLogicalChild(
-            IControl control,
-            IAvaloniaList<ILogical> logicalChildren,
-            object oldValue, 
-            object newValue)
-        {
-            if (oldValue != newValue)
-            {
-                if (oldValue is IControl child)
-                {
-                    logicalChildren.Remove(child);
-                    ((ISetInheritanceParent)child).SetParent(child.Parent);
-                }
-
-                child = newValue as IControl;
-
-                if (child != null && !logicalChildren.Contains(child))
-                {
-                    child.SetValue(Control.TemplatedParentProperty, control.TemplatedParent);
-                    logicalChildren.Add(child);
-                }
-            }
-        }
-    }
-}

+ 1 - 1
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@@ -377,7 +377,7 @@ namespace Avalonia.Controls.Platform
 
             if (mouse?.Type == RawPointerEventType.NonClientLeftButtonDown)
             {
-                Menu.Close();
+                Menu?.Close();
             }
         }
 

+ 12 - 35
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@@ -6,6 +6,7 @@ using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Templates;
 using Avalonia.Controls.Utils;
 using Avalonia.Data;
+using Avalonia.Input;
 using Avalonia.Layout;
 using Avalonia.LogicalTree;
 using Avalonia.Media;
@@ -83,7 +84,6 @@ namespace Avalonia.Controls.Presenters
 
         private IControl _child;
         private bool _createdChild;
-        EventHandler<AvaloniaPropertyChangedEventArgs> _childChanging;
         private IDataTemplate _dataTemplate;
         private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper();
 
@@ -190,12 +190,10 @@ namespace Avalonia.Controls.Presenters
             set { SetValue(PaddingProperty, value); }
         }
 
-        /// <inheritdoc/>
-        event EventHandler<AvaloniaPropertyChangedEventArgs> IContentPresenter.ChildChanging
-        {
-            add => _childChanging += value;
-            remove => _childChanging -= value;
-        }
+        /// <summary>
+        /// Gets the host content control.
+        /// </summary>
+        internal IContentPresenterHost Host { get; private set; }
 
         /// <inheritdoc/>
         public sealed override void ApplyTemplate()
@@ -222,34 +220,16 @@ namespace Avalonia.Controls.Presenters
             var content = Content;
             var oldChild = Child;
             var newChild = CreateChild();
+            var logicalChildren = Host?.LogicalChildren ?? LogicalChildren;
 
             // Remove the old child if we're not recycling it.
             if (newChild != oldChild)
             {
+
                 if (oldChild != null)
                 {
                     VisualChildren.Remove(oldChild);
-                }
-
-                if (oldChild?.Parent == this)
-                {
-                    // If we're the child's parent then the presenter isn't in a ContentControl's
-                    // template.
-                    LogicalChildren.Remove(oldChild);
-                }
-                else if (TemplatedParent != null)
-                {
-                    // If we're in a ContentControl's template then invoke ChildChanging to let
-                    // ContentControlMixin handle removing the logical child.
-                    _childChanging?.Invoke(this, new AvaloniaPropertyChangedEventArgs(
-                        this,
-                        ChildProperty,
-                        oldChild,
-                        newChild,
-                        BindingPriority.LocalValue));
-                }
-                else if (oldChild != null)
-                {
+                    logicalChildren.Remove(oldChild);
                     ((ISetInheritanceParent)oldChild).SetParent(oldChild.Parent);
                 }
             }
@@ -272,15 +252,11 @@ namespace Avalonia.Controls.Presenters
             else if (newChild != oldChild)
             {
                 ((ISetInheritanceParent)newChild).SetParent(this);
-
                 Child = newChild;
 
-                // If we're in a ContentControl's template then the child's parent will have been
-                // set by ContentControlMixin in response to Child changing. If not, then we're
-                // standalone and should make the control our own logical child.
-                if (newChild.Parent == null && TemplatedParent == null)
+                if (!logicalChildren.Contains(newChild))
                 {
-                    LogicalChildren.Add(newChild);
+                    logicalChildren.Add(newChild);
                 }
 
                 VisualChildren.Add(newChild);
@@ -459,7 +435,8 @@ namespace Avalonia.Controls.Presenters
 
         private void TemplatedParentChanged(AvaloniaPropertyChangedEventArgs e)
         {
-            (e.NewValue as IContentPresenterHost)?.RegisterContentPresenter(this);
+            var host = e.NewValue as IContentPresenterHost;
+            Host = host?.RegisterContentPresenter(this) == true ? host : null;
         }
     }
 }

+ 0 - 13
src/Avalonia.Controls/Presenters/IContentPresenter.cs

@@ -1,8 +1,6 @@
 // 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 Avalonia.Controls.Mixins;
 using Avalonia.Controls.Primitives;
 
 namespace Avalonia.Controls.Presenters
@@ -22,16 +20,5 @@ namespace Avalonia.Controls.Presenters
         /// Gets or sets the content to be displayed by the presenter.
         /// </summary>
         object Content { get; set; }
-
-        /// <summary>
-        /// Raised when <see cref="Child"/> property is about to change.
-        /// </summary>
-        /// <remarks>
-        /// This event should be raised after the child has been removed from the visual tree,
-        /// but before the <see cref="Child"/> property has changed. It is intended for consumption
-        /// by <see cref="ContentControlMixin"/> in order to update the host control's logical
-        /// children.
-        /// </remarks>
-        event EventHandler<AvaloniaPropertyChangedEventArgs> ChildChanging;
     }
 }

+ 12 - 1
src/Avalonia.Controls/Presenters/IContentPresenterHost.cs

@@ -1,6 +1,8 @@
 // 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 Avalonia.Collections;
+using Avalonia.LogicalTree;
 using Avalonia.Styling;
 
 namespace Avalonia.Controls.Presenters
@@ -18,10 +20,19 @@ namespace Avalonia.Controls.Presenters
     /// </remarks>
     public interface IContentPresenterHost : ITemplatedControl
     {
+        /// <summary>
+        /// Gets a collection describing the logical children of the host control.
+        /// </summary>
+        IAvaloniaList<ILogical> LogicalChildren { get; }
+
         /// <summary>
         /// Registers an <see cref="IContentPresenter"/> with a host control.
         /// </summary>
         /// <param name="presenter">The content presenter.</param>
-        void RegisterContentPresenter(IContentPresenter presenter);
+        /// <returns>
+        /// True if the content presenter should add its child to the logical children of the
+        /// host; otherwise false.
+        /// </returns>
+        bool RegisterContentPresenter(IContentPresenter presenter);
     }
 }

+ 20 - 6
src/Avalonia.Controls/Primitives/HeaderedContentControl.cs

@@ -4,6 +4,7 @@
 using Avalonia.Controls.Mixins;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Templates;
+using Avalonia.LogicalTree;
 
 namespace Avalonia.Controls.Primitives
 {
@@ -29,10 +30,7 @@ namespace Avalonia.Controls.Primitives
         /// </summary>
         static HeaderedContentControl()
         {
-            ContentControlMixin.Attach<HeaderedContentControl>(
-                HeaderProperty,
-                x => x.LogicalChildren,
-                "PART_HeaderPresenter");
+            ContentProperty.Changed.AddClassHandler<HeaderedContentControl>(x => x.HeaderChanged);
         }
 
         /// <summary>
@@ -63,13 +61,29 @@ namespace Avalonia.Controls.Primitives
         }
 
         /// <inheritdoc/>
-        protected override void RegisterContentPresenter(IContentPresenter presenter)
+        protected override bool RegisterContentPresenter(IContentPresenter presenter)
         {
-            base.RegisterContentPresenter(presenter);
+            var result = base.RegisterContentPresenter(presenter);
 
             if (presenter.Name == "PART_HeaderPresenter")
             {
                 HeaderPresenter = presenter;
+                result = true;
+            }
+
+            return result;
+        }
+
+        private void HeaderChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (e.OldValue is ILogical oldChild)
+            {
+                LogicalChildren.Remove(oldChild);
+            }
+
+            if (e.NewValue is ILogical newChild)
+            {
+                LogicalChildren.Add(newChild);
             }
         }
     }

+ 25 - 7
src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs

@@ -1,8 +1,10 @@
 // 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 Avalonia.Collections;
 using Avalonia.Controls.Mixins;
 using Avalonia.Controls.Presenters;
+using Avalonia.LogicalTree;
 
 namespace Avalonia.Controls.Primitives
 {
@@ -22,10 +24,7 @@ namespace Avalonia.Controls.Primitives
         /// </summary>
         static HeaderedItemsControl()
         {
-            ContentControlMixin.Attach<HeaderedItemsControl>(
-                HeaderProperty,
-                x => x.LogicalChildren,
-                "PART_HeaderPresenter");
+            HeaderProperty.Changed.AddClassHandler<HeaderedItemsControl>(x => x.HeaderChanged);
         }
 
         /// <summary>
@@ -47,20 +46,39 @@ namespace Avalonia.Controls.Primitives
         }
 
         /// <inheritdoc/>
-        void IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter)
+        IAvaloniaList<ILogical> IContentPresenterHost.LogicalChildren => LogicalChildren;
+
+        /// <inheritdoc/>
+        bool IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter)
         {
-            RegisterContentPresenter(presenter);
+            return RegisterContentPresenter(presenter);
         }
 
         /// <summary>
         /// Called when an <see cref="IContentPresenter"/> is registered with the control.
         /// </summary>
         /// <param name="presenter">The presenter.</param>
-        protected virtual void RegisterContentPresenter(IContentPresenter presenter)
+        protected virtual bool RegisterContentPresenter(IContentPresenter presenter)
         {
             if (presenter.Name == "PART_HeaderPresenter")
             {
                 HeaderPresenter = presenter;
+                return true;
+            }
+
+            return false;
+        }
+
+        private void HeaderChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (e.OldValue is ILogical oldChild)
+            {
+                LogicalChildren.Remove(oldChild);
+            }
+
+            if (e.NewValue is ILogical newChild)
+            {
+                LogicalChildren.Add(newChild);
             }
         }
     }

+ 25 - 7
src/Avalonia.Controls/Primitives/HeaderedSelectingItemsControl.cs

@@ -1,8 +1,10 @@
 // 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 Avalonia.Collections;
 using Avalonia.Controls.Mixins;
 using Avalonia.Controls.Presenters;
+using Avalonia.LogicalTree;
 
 namespace Avalonia.Controls.Primitives
 {
@@ -22,10 +24,7 @@ namespace Avalonia.Controls.Primitives
         /// </summary>
         static HeaderedSelectingItemsControl()
         {
-            ContentControlMixin.Attach<HeaderedSelectingItemsControl>(
-                HeaderProperty,
-                x => x.LogicalChildren,
-                "PART_HeaderPresenter");
+            HeaderProperty.Changed.AddClassHandler<HeaderedSelectingItemsControl>(x => x.HeaderChanged);
         }
 
         /// <summary>
@@ -47,20 +46,39 @@ namespace Avalonia.Controls.Primitives
         }
 
         /// <inheritdoc/>
-        void IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter)
+        IAvaloniaList<ILogical> IContentPresenterHost.LogicalChildren => LogicalChildren;
+
+        /// <inheritdoc/>
+        bool IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter)
         {
-            RegisterContentPresenter(presenter);
+            return RegisterContentPresenter(presenter);
         }
 
         /// <summary>
         /// Called when an <see cref="IContentPresenter"/> is registered with the control.
         /// </summary>
         /// <param name="presenter">The presenter.</param>
-        protected virtual void RegisterContentPresenter(IContentPresenter presenter)
+        protected virtual bool RegisterContentPresenter(IContentPresenter presenter)
         {
             if (presenter.Name == "PART_HeaderPresenter")
             {
                 HeaderPresenter = presenter;
+                return true;
+            }
+
+            return false;
+        }
+
+        private void HeaderChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (e.OldValue is ILogical oldChild)
+            {
+                LogicalChildren.Remove(oldChild);
+            }
+
+            if (e.NewValue is ILogical newChild)
+            {
+                LogicalChildren.Add(newChild);
             }
         }
     }

+ 28 - 8
src/Avalonia.Controls/TabControl.cs

@@ -2,6 +2,7 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System.Linq;
+using Avalonia.Collections;
 using Avalonia.Controls.Generators;
 using Avalonia.Controls.Mixins;
 using Avalonia.Controls.Presenters;
@@ -9,6 +10,7 @@ using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Templates;
 using Avalonia.Input;
 using Avalonia.Layout;
+using Avalonia.LogicalTree;
 using Avalonia.VisualTree;
 
 namespace Avalonia.Controls
@@ -16,7 +18,7 @@ namespace Avalonia.Controls
     /// <summary>
     /// A tab control that displays a tab strip along with the content of the selected tab.
     /// </summary>
-    public class TabControl : SelectingItemsControl
+    public class TabControl : SelectingItemsControl, IContentPresenterHost
     {
         /// <summary>
         /// Defines the <see cref="TabStripPlacement"/> property.
@@ -68,10 +70,6 @@ namespace Avalonia.Controls
             SelectionModeProperty.OverrideDefaultValue<TabControl>(SelectionMode.AlwaysSelected);
             ItemsPanelProperty.OverrideDefaultValue<TabControl>(DefaultPanel);
             AffectsMeasure<TabControl>(TabStripPlacementProperty);
-            ContentControlMixin.Attach<TabControl>(
-                SelectedContentProperty,
-                x => x.LogicalChildren,
-                "PART_SelectedContentHost");
         }
 
         /// <summary>
@@ -136,7 +134,31 @@ namespace Avalonia.Controls
 
         internal ItemsPresenter ItemsPresenterPart { get; private set; }
 
-        internal ContentPresenter ContentPart { get; private set; }
+        internal IContentPresenter ContentPart { get; private set; }
+
+        /// <inheritdoc/>
+        IAvaloniaList<ILogical> IContentPresenterHost.LogicalChildren => LogicalChildren;
+
+        /// <inheritdoc/>
+        bool IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter)
+        {
+            return RegisterContentPresenter(presenter);
+        }
+
+        /// <summary>
+        /// Called when an <see cref="IContentPresenter"/> is registered with the control.
+        /// </summary>
+        /// <param name="presenter">The presenter.</param>
+        protected virtual bool RegisterContentPresenter(IContentPresenter presenter)
+        {
+            if (presenter.Name == "PART_SelectedContentHost")
+            {
+                ContentPart = presenter;
+                return true;
+            }
+
+            return false;
+        }
 
         protected override IItemContainerGenerator CreateItemContainerGenerator()
         {
@@ -148,8 +170,6 @@ namespace Avalonia.Controls
             base.OnTemplateApplied(e);
 
             ItemsPresenterPart = e.NameScope.Get<ItemsPresenter>("PART_ItemsPresenter");
-
-            ContentPart = e.NameScope.Get<ContentPresenter>("PART_SelectedContentHost");
         }
 
         /// <inheritdoc/>

+ 4 - 1
src/Avalonia.Diagnostics/Views/TreePageView.xaml.cs

@@ -70,7 +70,10 @@ namespace Avalonia.Diagnostics.Views
         private void TreeViewItemTemplateApplied(object sender, TemplateAppliedEventArgs e)
         {
             var item = (TreeViewItem)sender;
-            var header = item.HeaderPresenter.Child;
+            var headerPresenter = item.HeaderPresenter;
+            headerPresenter.ApplyTemplate();
+
+            var header = headerPresenter.Child;
             header.PointerEnter += AddAdorner;
             header.PointerLeave += RemoveAdorner;
             item.TemplateApplied -= TreeViewItemTemplateApplied;

+ 1 - 0
tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs

@@ -984,6 +984,7 @@ namespace Avalonia.Controls.UnitTests
                 TextBox textBox = GetTextBox(control);
                 var window = new Window {Content = control};
                 window.ApplyTemplate();
+                window.Presenter.ApplyTemplate();
                 Dispatcher.UIThread.RunJobs();
                 test.Invoke(control, textBox);
             }

+ 40 - 0
tests/Avalonia.Controls.UnitTests/ContentControlTests.cs

@@ -50,6 +50,7 @@ namespace Avalonia.Controls.UnitTests
             root.Child = target;
 
             target.ApplyTemplate();
+            target.Presenter.ApplyTemplate();
 
             styler.Verify(x => x.ApplyStyles(It.IsAny<ContentControl>()), Times.Once());
             styler.Verify(x => x.ApplyStyles(It.IsAny<Border>()), Times.Once());
@@ -331,6 +332,45 @@ namespace Avalonia.Controls.UnitTests
             Assert.Null(textBlock.GetLogicalParent());
         }
 
+        [Fact]
+        public void Should_Set_Child_LogicalParent_After_Removing_And_Adding_Back_To_Logical_Tree()
+        {
+            using (UnitTestApplication.Start(TestServices.RealStyler))
+            {
+                var target = new ContentControl();
+                var root = new TestRoot
+                {
+                    Styles =
+                    {
+                        new Style(x => x.OfType<ContentControl>())
+                        {
+                            Setters =
+                            {
+                                new Setter(ContentControl.TemplateProperty, GetTemplate()),
+                            }
+                        }
+                    },
+                    Child = target
+                };
+
+                target.Content = "Foo";
+                target.ApplyTemplate();
+                target.Presenter.ApplyTemplate();
+
+                Assert.Equal(target, target.Presenter.Child.LogicalParent);
+
+                root.Child = null;
+
+                Assert.Null(target.Template);
+
+                target.Content = null;
+                root.Child = target;
+                target.Content = "Bar";
+
+                Assert.Equal(target, target.Presenter.Child.LogicalParent);
+            }
+        }
+
         private FuncControlTemplate GetTemplate()
         {
             return new FuncControlTemplate<ContentControl>((parent, scope) =>

+ 9 - 3
tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs

@@ -27,7 +27,9 @@ namespace Avalonia.Controls.UnitTests
                     ContextMenu = sut
                 };
 
-                new Window { Content = target }.ApplyTemplate();
+                var window = new Window { Content = target };
+                window.ApplyTemplate();
+                window.Presenter.ApplyTemplate();
 
                 int openedCount = 0;
 
@@ -53,7 +55,9 @@ namespace Avalonia.Controls.UnitTests
                     ContextMenu = sut
                 };
 
-                new Window { Content = target }.ApplyTemplate();
+                var window = new Window { Content = target };
+                window.ApplyTemplate();
+                window.Presenter.ApplyTemplate();
 
                 sut.Open(target);
 
@@ -86,6 +90,7 @@ namespace Avalonia.Controls.UnitTests
 
                 var window = new Window {Content = target};
                 window.ApplyTemplate();
+                window.Presenter.ApplyTemplate();
 
                 _mouse.Click(target, MouseButton.Right);
 
@@ -115,7 +120,8 @@ namespace Avalonia.Controls.UnitTests
 
                 var window = new Window {Content = target};
                 window.ApplyTemplate();
-                
+                window.Presenter.ApplyTemplate();
+
                 _mouse.Click(target, MouseButton.Right);
 
                 Assert.True(sut.IsOpen);

+ 0 - 107
tests/Avalonia.Controls.UnitTests/Mixins/ContentControlMixinTests.cs

@@ -1,107 +0,0 @@
-// 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.Collections.Generic;
-using System.Linq;
-using Avalonia.Collections;
-using Avalonia.Controls.Mixins;
-using Avalonia.Controls.Presenters;
-using Avalonia.Controls.Primitives;
-using Avalonia.Controls.Templates;
-using Avalonia.LogicalTree;
-using Moq;
-using Xunit;
-
-namespace Avalonia.Controls.UnitTests.Mixins
-{
-    public class ContentControlMixinTests
-    {
-        [Fact]
-        public void Multiple_Mixin_Usages_Should_Not_Throw()
-        {
-            var target = new TestControl()
-            {
-                Template = new FuncControlTemplate((_, scope) => new Panel
-                {
-                    Children =
-                    {
-                        new ContentPresenter {Name = "Content_1_Presenter"}.RegisterInNameScope(scope),
-                        new ContentPresenter {Name = "Content_2_Presenter"}.RegisterInNameScope(scope)
-                    }
-                })
-            };
-            
-
-            var ex = Record.Exception(() => target.ApplyTemplate());
-
-            Assert.Null(ex);
-        }
-
-        [Fact]
-        public void Replacing_Template_Releases_Events()
-        {
-            var p1 = new ContentPresenter { Name = "Content_1_Presenter" };
-            var p2 = new ContentPresenter { Name = "Content_2_Presenter" };
-            var target = new TestControl
-            {
-                Template = new FuncControlTemplate((_, scope) => new Panel
-                {
-                    Children =
-                    {
-                        p1.RegisterInNameScope(scope),
-                        p2.RegisterInNameScope(scope)
-                    }
-                })
-            };
-            target.ApplyTemplate();
-
-            Control tc;
-
-            p1.Content = tc = new Control();
-            p1.UpdateChild();
-            Assert.Contains(tc, target.GetLogicalChildren());
-
-            p2.Content = tc = new Control();
-            p2.UpdateChild();
-            Assert.Contains(tc, target.GetLogicalChildren());
-
-            target.Template = null;
-
-            p1.Content = tc = new Control();
-            p1.UpdateChild();
-            Assert.DoesNotContain(tc, target.GetLogicalChildren());
-
-            p2.Content = tc = new Control();
-            p2.UpdateChild();
-            Assert.DoesNotContain(tc, target.GetLogicalChildren());
-
-        }
-
-        private class TestControl : TemplatedControl
-        {
-            public static readonly StyledProperty<object> Content1Property =
-                AvaloniaProperty.Register<TestControl, object>(nameof(Content1));
-
-            public static readonly StyledProperty<object> Content2Property =
-                AvaloniaProperty.Register<TestControl, object>(nameof(Content2));
-
-            static TestControl()
-            {
-                ContentControlMixin.Attach<TestControl>(Content1Property, x => x.LogicalChildren, "Content_1_Presenter");
-                ContentControlMixin.Attach<TestControl>(Content2Property, x => x.LogicalChildren, "Content_2_Presenter");
-            }
-
-            public object Content1
-            {
-                get { return GetValue(Content1Property); }
-                set { SetValue(Content1Property, value); }
-            }
-
-            public object Content2
-            {
-                get { return GetValue(Content2Property); }
-                set { SetValue(Content2Property, value); }
-            }
-        }
-    }
-}

+ 13 - 0
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs

@@ -343,6 +343,19 @@ namespace Avalonia.Controls.UnitTests.Presenters
             Assert.Same(logicalParent, ((IStyledElement)child).StylingParent);
         }
 
+        [Fact]
+        public void Should_Clear_Host_When_Host_Template_Cleared()
+        {
+            var (target, host) = CreateTarget();
+
+            Assert.Same(host, target.Host);
+
+            host.Template = null;
+            host.ApplyTemplate();
+
+            Assert.Null(target.Host);
+        }
+
         (ContentPresenter presenter, ContentControl templatedParent) CreateTarget()
         {
             var templatedParent = new ContentControl

+ 2 - 0
tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs

@@ -51,6 +51,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
                 window.Content = target;
 
                 window.ApplyTemplate();
+                window.Presenter.ApplyTemplate();
                 target.ApplyTemplate();
                 target.Popup.Open();
 
@@ -167,6 +168,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
                 window.Content = target;
 
                 window.ApplyTemplate();
+                window.Presenter.ApplyTemplate();
                 target.ApplyTemplate();
                 target.Popup.Open();
                 target.PopupContent = null;

+ 6 - 6
tests/Avalonia.Controls.UnitTests/TabControlTests.cs

@@ -183,27 +183,27 @@ namespace Avalonia.Controls.UnitTests
 
             ApplyTemplate(target);
 
-            target.ContentPart.UpdateChild();
+            ((ContentPresenter)target.ContentPart).UpdateChild();
             var dataContext = ((TextBlock)target.ContentPart.Child).DataContext;
             Assert.Equal(items[0], dataContext);
 
             target.SelectedIndex = 1;
-            target.ContentPart.UpdateChild();
+            ((ContentPresenter)target.ContentPart).UpdateChild();
             dataContext = ((Button)target.ContentPart.Child).DataContext;
             Assert.Equal(items[1], dataContext);
 
             target.SelectedIndex = 2;
-            target.ContentPart.UpdateChild();
+            ((ContentPresenter)target.ContentPart).UpdateChild();
             dataContext = ((TextBlock)target.ContentPart.Child).DataContext;
             Assert.Equal("Base", dataContext);
 
             target.SelectedIndex = 3;
-            target.ContentPart.UpdateChild();
+            ((ContentPresenter)target.ContentPart).UpdateChild();
             dataContext = ((TextBlock)target.ContentPart.Child).DataContext;
             Assert.Equal("Qux", dataContext);
 
             target.SelectedIndex = 4;
-            target.ContentPart.UpdateChild();
+            ((ContentPresenter)target.ContentPart).UpdateChild();
             dataContext = target.ContentPart.DataContext;
             Assert.Equal("Base", dataContext);
         }
@@ -279,7 +279,7 @@ namespace Avalonia.Controls.UnitTests
             };
 
             ApplyTemplate(target);
-            target.ContentPart.UpdateChild();
+            ((ContentPresenter)target.ContentPart).UpdateChild();
 
             var content = Assert.IsType<TextBlock>(target.ContentPart.Child);
             Assert.Equal("bar", content.Tag);

+ 2 - 1
tests/Avalonia.Controls.UnitTests/TopLevelTests.cs

@@ -202,8 +202,9 @@ namespace Avalonia.Controls.UnitTests
 
                 target.Template = CreateTemplate();
                 target.Content = child;
+                target.ApplyTemplate();
 
-                Assert.Throws<InvalidOperationException>(() => target.ApplyTemplate());
+                Assert.Throws<InvalidOperationException>(() => target.Presenter.ApplyTemplate());
             }
         }
 

+ 1 - 0
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs

@@ -142,6 +142,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
 
                 window.DataContext = new { Foo = "foo" };
                 window.ApplyTemplate();
+                window.Presenter.ApplyTemplate();
 
                 Assert.Equal("foo", border.DataContext);
             }

+ 3 - 0
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests_RelativeSource.cs

@@ -73,6 +73,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
                 var button = window.FindControl<Button>("button");
 
                 window.ApplyTemplate();
+                window.Presenter.ApplyTemplate();
 
                 Assert.Equal("border2", button.Content);
             }
@@ -169,6 +170,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
                 var button = window.FindControl<Button>("button");
 
                 window.ApplyTemplate();
+                window.Presenter.ApplyTemplate();
 
                 Assert.Equal("border1", button.Content);
             }
@@ -293,6 +295,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
                 var button = window.FindControl<Button>("button");
 
                 window.ApplyTemplate();
+                window.Presenter.ApplyTemplate();
 
                 Assert.Equal("title", button.Content);
             }