diff --git a/src/Avalonia.Base/Layout/Layoutable.cs b/src/Avalonia.Base/Layout/Layoutable.cs index 73c9e1ba0f..53a3c84364 100644 --- a/src/Avalonia.Base/Layout/Layoutable.cs +++ b/src/Avalonia.Base/Layout/Layoutable.cs @@ -135,6 +135,7 @@ namespace Avalonia.Layout private Rect? _previousArrange; private EventHandler? _effectiveViewportChanged; private EventHandler? _layoutUpdated; + private bool _isAttachingToVisualTree; /// /// Initializes static members of the class. @@ -164,7 +165,7 @@ namespace Avalonia.Layout { add { - if (_effectiveViewportChanged is null && VisualRoot is ILayoutRoot r) + if (_effectiveViewportChanged is null && VisualRoot is ILayoutRoot r && !_isAttachingToVisualTree) { r.LayoutManager.RegisterEffectiveViewportListener(this); } @@ -190,7 +191,7 @@ namespace Avalonia.Layout { add { - if (_layoutUpdated is null && VisualRoot is ILayoutRoot r) + if (_layoutUpdated is null && VisualRoot is ILayoutRoot r && !_isAttachingToVisualTree) { r.LayoutManager.LayoutUpdated += LayoutManagedLayoutUpdated; } @@ -735,9 +736,18 @@ namespace Avalonia.Layout InvalidateMeasure(); } + /// protected override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e) { - base.OnAttachedToVisualTreeCore(e); + _isAttachingToVisualTree = true; + try + { + base.OnAttachedToVisualTreeCore(e); + } + finally + { + _isAttachingToVisualTree = false; + } if (e.Root is ILayoutRoot r) { diff --git a/tests/Avalonia.Base.UnitTests/Layout/LayoutableTests.cs b/tests/Avalonia.Base.UnitTests/Layout/LayoutableTests.cs index ab1c036b72..b8cba8c169 100644 --- a/tests/Avalonia.Base.UnitTests/Layout/LayoutableTests.cs +++ b/tests/Avalonia.Base.UnitTests/Layout/LayoutableTests.cs @@ -291,6 +291,30 @@ namespace Avalonia.Base.UnitTests.Layout Times.Once); } + [Fact] + public void LayoutManager_LayoutUpdated_Should_Not_Be_Subscribed_Twice_In_AttachedToVisualTree() + { + Border border1; + var layoutManager = new Mock(); + layoutManager.SetupAdd(m => m.LayoutUpdated += (_, _) => { }); + + _ = new TestRoot + { + Child = border1 = new Border(), + LayoutManager = layoutManager.Object, + }; + + var border2 = new Border(); + border2.AttachedToVisualTree += (_, _) => border2.LayoutUpdated += (_, _) => { }; + + layoutManager.Invocations.Clear(); + border1.Child = border2; + + layoutManager.VerifyAdd( + x => x.LayoutUpdated += It.IsAny(), + Times.Once); + } + [Fact] public void Making_Control_Invisible_Should_Invalidate_Parent_Measure() { diff --git a/tests/Avalonia.Base.UnitTests/Layout/LayoutableTests_EffectiveViewportChanged.cs b/tests/Avalonia.Base.UnitTests/Layout/LayoutableTests_EffectiveViewportChanged.cs index 63eb6589ca..94054f5245 100644 --- a/tests/Avalonia.Base.UnitTests/Layout/LayoutableTests_EffectiveViewportChanged.cs +++ b/tests/Avalonia.Base.UnitTests/Layout/LayoutableTests_EffectiveViewportChanged.cs @@ -57,6 +57,28 @@ namespace Avalonia.Base.UnitTests.Layout }); } + [Fact] + public async Task EffectiveViewportChanged_Should_Not_Be_Raised_Twice_If_Subcribed_In_AttachedToVisualTree() + { + await RunOnUIThread.Execute(async () => + { + var root = CreateRoot(); + var target = new Canvas(); + var raised = 0; + + target.AttachedToVisualTree += (_, _) => + { + target.EffectiveViewportChanged += (_, _) => ++raised; + }; + + root.Child = target; + + await ExecuteInitialLayoutPass(root); + + Assert.Equal(1, raised); + }); + } + [Fact] public async Task Parent_Affects_EffectiveViewport() {