diff --git a/src/Avalonia.Controls/ContentControl.cs b/src/Avalonia.Controls/ContentControl.cs index b8a45e102f..d47a7a7809 100644 --- a/src/Avalonia.Controls/ContentControl.cs +++ b/src/Avalonia.Controls/ContentControl.cs @@ -116,14 +116,19 @@ namespace Avalonia.Controls return false; } - private void ContentChanged(AvaloniaPropertyChangedEventArgs e) + protected virtual void ContentChanged(AvaloniaPropertyChangedEventArgs e) { - if (e.OldValue is ILogical oldChild) + UpdateLogicalTree(e.OldValue, e.NewValue); + } + + protected void UpdateLogicalTree(object? toRemove, object? toAdd) + { + if (toRemove is ILogical oldChild) { LogicalChildren.Remove(oldChild); } - if (e.NewValue is ILogical newChild) + if (toAdd is ILogical newChild) { LogicalChildren.Add(newChild); } diff --git a/src/Avalonia.Controls/TransitioningContentControl.cs b/src/Avalonia.Controls/TransitioningContentControl.cs index 70b21b7248..545032befb 100644 --- a/src/Avalonia.Controls/TransitioningContentControl.cs +++ b/src/Avalonia.Controls/TransitioningContentControl.cs @@ -71,6 +71,11 @@ public class TransitioningContentControl : ContentControl } } + protected override void ContentChanged(AvaloniaPropertyChangedEventArgs e) + { + // We do nothing becuse we should not remove old Content until the animation is over + } + /// /// Updates the content with transitions. /// @@ -89,6 +94,8 @@ public class TransitioningContentControl : ContentControl if (PageTransition != null) await PageTransition.Start(this, null, true, localToken); + UpdateLogicalTree(CurrentContent, content); + if (localToken.IsCancellationRequested) { return; diff --git a/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs b/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs new file mode 100644 index 0000000000..fa523d7f78 --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs @@ -0,0 +1,63 @@ +using System; +using Avalonia.LogicalTree; +using Avalonia.UnitTests; +using Xunit; +using System.Threading; +using System.Threading.Tasks; +using Avalonia.Animation; + +namespace Avalonia.Controls.UnitTests +{ + public class TransitioningContentControlTests + { + [Fact] + public void Old_Content_Shuold_Be_Removed__From_Logical_Tree_After_Out_Animation() + { + var testTransition = new TestTransition(); + + var target = new TransitioningContentControl(); + target.PageTransition = testTransition; + + var root = new TestRoot() { Child = target }; + + var oldControl = new Control(); + var newControl = new Control(); + + target.Content = oldControl; + Threading.Dispatcher.UIThread.RunJobs(); + + Assert.Equal(target, oldControl.GetLogicalParent()); + Assert.Equal(null, newControl.GetLogicalParent()); + + testTransition.BeginTransition += isFrom => + { + // Old out + if (isFrom) + { + Assert.Equal(target, oldControl.GetLogicalParent()); + Assert.Equal(null, newControl.GetLogicalParent()); + } + // New in + else + { + Assert.Equal(null, oldControl.GetLogicalParent()); + Assert.Equal(target, newControl.GetLogicalParent()); + } + }; + + target.Content = newControl; + Threading.Dispatcher.UIThread.RunJobs(); + } + } + public class TestTransition : IPageTransition + { + public event Action BeginTransition; + + public Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken) + { + bool isFrom = from != null && to == null; + BeginTransition?.Invoke(isFrom); + return Task.CompletedTask; + } + } +}