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
+}