diff --git a/src/Avalonia.Controls/Mixins/ContentControlMixin.cs b/src/Avalonia.Controls/Mixins/ContentControlMixin.cs index c4da00f5d0..25b29e37e6 100644 --- a/src/Avalonia.Controls/Mixins/ContentControlMixin.cs +++ b/src/Avalonia.Controls/Mixins/ContentControlMixin.cs @@ -19,8 +19,8 @@ namespace Avalonia.Controls.Mixins /// /// The adds behavior to a control which acts as a content /// control such as and . It - /// updates keeps the control's logical children in sync with the content being displayed by - /// the control. + /// keeps the control's logical children in sync with the content being displayed by the + /// control. /// public class ContentControlMixin { @@ -49,25 +49,42 @@ namespace Avalonia.Controls.Mixins Contract.Requires(content != null); Contract.Requires(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 = (IControl)e.NameScope.Find(presenterName); + var presenter = e.NameScope.Find(presenterName) as IContentPresenter; if (presenter != null) { presenter.ApplyTemplate(); var logicalChildren = logicalChildrenSelector(sender); - var subscription = presenter + 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, - c.OldValue, - c.NewValue)); + null, + c.NewValue))); UpdateLogicalChild( sender, diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs index 83d8616e90..49f268c128 100644 --- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs @@ -5,6 +5,7 @@ using System; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Controls.Utils; +using Avalonia.Data; using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.Media; @@ -82,6 +83,7 @@ namespace Avalonia.Controls.Presenters private IControl _child; private bool _createdChild; + EventHandler _childChanging; private IDataTemplate _dataTemplate; private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper(); @@ -188,6 +190,13 @@ namespace Avalonia.Controls.Presenters set { SetValue(PaddingProperty, value); } } + /// + event EventHandler IContentPresenter.ChildChanging + { + add => _childChanging += value; + remove => _childChanging -= value; + } + /// public sealed override void ApplyTemplate() { @@ -215,9 +224,30 @@ namespace Avalonia.Controls.Presenters var newChild = CreateChild(); // Remove the old child if we're not recycling it. - if (oldChild != null && newChild != oldChild) + if (newChild != oldChild) { - VisualChildren.Remove(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 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)); + } } // Set the DataContext if the data isn't a control. @@ -241,11 +271,9 @@ namespace Avalonia.Controls.Presenters Child = newChild; - if (oldChild?.Parent == this) - { - LogicalChildren.Remove(oldChild); - } - + // 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) { LogicalChildren.Add(newChild); diff --git a/src/Avalonia.Controls/Presenters/IContentPresenter.cs b/src/Avalonia.Controls/Presenters/IContentPresenter.cs index 3b8039f33c..78bffec93b 100644 --- a/src/Avalonia.Controls/Presenters/IContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/IContentPresenter.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 System; +using Avalonia.Controls.Mixins; using Avalonia.Controls.Primitives; namespace Avalonia.Controls.Presenters @@ -20,5 +22,16 @@ namespace Avalonia.Controls.Presenters /// Gets or sets the content to be displayed by the presenter. /// object Content { get; set; } + + /// + /// Raised when property is about to change. + /// + /// + /// This event should be raised after the child has been removed from the visual tree, + /// but before the property has changed. It is intended for consumption + /// by in order to update the host control's logical + /// children. + /// + event EventHandler ChildChanging; } -} \ No newline at end of file +}