From 7694fc048437a172cef98339bc037dd652f1d838 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 23 Jun 2020 16:40:19 +0200 Subject: [PATCH 1/5] Don't run layout passes on hidden TopLevels. To do this we need to pass the root to `LayoutManager`. Fixes #4161 --- src/Avalonia.Controls/TopLevel.cs | 2 +- src/Avalonia.Layout/ILayoutManager.cs | 10 +++++++ src/Avalonia.Layout/LayoutManager.cs | 28 +++++++++++++++---- .../ItemsPresenterTests_Virtualization.cs | 7 ++++- ...emsPresenterTests_Virtualization_Simple.cs | 7 ++++- .../TopLevelTests.cs | 2 +- .../LayoutableTests.cs | 4 +-- tests/Avalonia.UnitTests/TestRoot.cs | 4 ++- tests/Avalonia.UnitTests/TestTemplatedRoot.cs | 3 +- 9 files changed, 53 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 5d34444eb8..c738a5ff3b 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -315,7 +315,7 @@ namespace Avalonia.Controls /// /// Creates the layout manager for this . /// - protected virtual ILayoutManager CreateLayoutManager() => new LayoutManager(); + protected virtual ILayoutManager CreateLayoutManager() => new LayoutManager(this); /// /// Handles a paint notification from . diff --git a/src/Avalonia.Layout/ILayoutManager.cs b/src/Avalonia.Layout/ILayoutManager.cs index 6e63d3edbb..688b6b83e5 100644 --- a/src/Avalonia.Layout/ILayoutManager.cs +++ b/src/Avalonia.Layout/ILayoutManager.cs @@ -35,6 +35,15 @@ namespace Avalonia.Layout /// void ExecuteLayoutPass(); + /// + /// Executes the initial layout pass on a layout root. + /// + /// + /// You should not usually need to call this method explictly, the layout root will call + /// it to carry out the initial layout of the control. + /// + void ExecuteInitialLayoutPass(); + /// /// Executes the initial layout pass on a layout root. /// @@ -43,6 +52,7 @@ namespace Avalonia.Layout /// You should not usually need to call this method explictly, the layout root will call /// it to carry out the initial layout of the control. /// + [Obsolete("Call ExecuteInitialLayoutPass without parameter")] void ExecuteInitialLayoutPass(ILayoutRoot root); } } diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index aefb319fd0..a81f2b61b7 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/src/Avalonia.Layout/LayoutManager.cs @@ -12,14 +12,16 @@ namespace Avalonia.Layout /// public class LayoutManager : ILayoutManager { + private readonly ILayoutRoot _owner; private readonly LayoutQueue _toMeasure = new LayoutQueue(v => !v.IsMeasureValid); private readonly LayoutQueue _toArrange = new LayoutQueue(v => !v.IsArrangeValid); private readonly Action _executeLayoutPass; private bool _queued; private bool _running; - public LayoutManager() + public LayoutManager(ILayoutRoot owner) { + _owner = owner ?? throw new ArgumentNullException(nameof(owner)); _executeLayoutPass = ExecuteLayoutPass; } @@ -73,6 +75,11 @@ namespace Avalonia.Layout Dispatcher.UIThread.VerifyAccess(); + if (!_owner.IsVisible) + { + return; + } + if (!_running) { _running = true; @@ -131,13 +138,13 @@ namespace Avalonia.Layout } /// - public virtual void ExecuteInitialLayoutPass(ILayoutRoot root) + public virtual void ExecuteInitialLayoutPass() { try { _running = true; - Measure(root); - Arrange(root); + Measure(_owner); + Arrange(_owner); } finally { @@ -151,6 +158,17 @@ namespace Avalonia.Layout ExecuteLayoutPass(); } + [Obsolete("Call ExecuteInitialLayoutPass without parameter")] + public void ExecuteInitialLayoutPass(ILayoutRoot root) + { + if (root != _owner) + { + throw new ArgumentException("ExecuteInitialLayoutPass called with incorrect root."); + } + + ExecuteInitialLayoutPass(); + } + private void ExecuteMeasurePass() { while (_toMeasure.Count > 0) @@ -228,7 +246,7 @@ namespace Avalonia.Layout private void QueueLayoutPass() { - if (!_queued && !_running) + if (!_queued && !_running && _owner.IsVisible) { Dispatcher.UIThread.Post(_executeLayoutPass, DispatcherPriority.Layout); _queued = true; diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs index 9caae89cfe..3320ced8a4 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs @@ -324,6 +324,11 @@ namespace Avalonia.Controls.UnitTests.Presenters private class TestScroller : ScrollContentPresenter, IRenderRoot, ILayoutRoot { + public TestScroller() + { + LayoutManager = new LayoutManager(this); + } + public IRenderer Renderer { get; } public Size ClientSize { get; } public double RenderScaling => 1; @@ -332,7 +337,7 @@ namespace Avalonia.Controls.UnitTests.Presenters public double LayoutScaling => 1; - public ILayoutManager LayoutManager { get; } = new LayoutManager(); + public ILayoutManager LayoutManager { get; } public IRenderTarget CreateRenderTarget() => throw new NotImplementedException(); public void Invalidate(Rect rect) => throw new NotImplementedException(); diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs index 5a2cb60a56..d3fa565f4c 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs @@ -1062,6 +1062,11 @@ namespace Avalonia.Controls.UnitTests.Presenters private class TestScroller : ScrollContentPresenter, IRenderRoot, ILayoutRoot, ILogicalRoot { + public TestScroller() + { + LayoutManager = new LayoutManager(this); + } + public IRenderer Renderer { get; } public Size ClientSize { get; } public double RenderScaling => 1; @@ -1070,7 +1075,7 @@ namespace Avalonia.Controls.UnitTests.Presenters public double LayoutScaling => 1; - public ILayoutManager LayoutManager { get; } = new LayoutManager(); + public ILayoutManager LayoutManager { get; } public IRenderTarget CreateRenderTarget() => throw new NotImplementedException(); public void Invalidate(Rect rect) => throw new NotImplementedException(); diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index 6b107b0187..e5ff8d04de 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -323,7 +323,7 @@ namespace Avalonia.Controls.UnitTests public TestTopLevel(ITopLevelImpl impl, ILayoutManager layoutManager = null) : base(impl) { - _layoutManager = layoutManager ?? new LayoutManager(); + _layoutManager = layoutManager ?? new LayoutManager(this); } protected override ILayoutManager CreateLayoutManager() => _layoutManager; diff --git a/tests/Avalonia.Layout.UnitTests/LayoutableTests.cs b/tests/Avalonia.Layout.UnitTests/LayoutableTests.cs index a21c8d589d..44a5af94b9 100644 --- a/tests/Avalonia.Layout.UnitTests/LayoutableTests.cs +++ b/tests/Avalonia.Layout.UnitTests/LayoutableTests.cs @@ -208,14 +208,12 @@ namespace Avalonia.Layout.UnitTests { Border border1; Border border2; - var layoutManager = new LayoutManager(); var root = new TestRoot { Child = border1 = new Border { Child = border2 = new Border(), }, - LayoutManager = layoutManager, }; var raised = 0; @@ -233,7 +231,7 @@ namespace Avalonia.Layout.UnitTests root.Measure(new Size(100, 100)); root.Arrange(new Rect(0, 0, 100, 100)); - layoutManager.ExecuteLayoutPass(); + root.LayoutManager.ExecuteLayoutPass(); Assert.Equal(3, raised); Assert.Equal(new Rect(0, 0, 100, 100), border1.Bounds); diff --git a/tests/Avalonia.UnitTests/TestRoot.cs b/tests/Avalonia.UnitTests/TestRoot.cs index f291d386aa..b6f3a020e8 100644 --- a/tests/Avalonia.UnitTests/TestRoot.cs +++ b/tests/Avalonia.UnitTests/TestRoot.cs @@ -19,6 +19,8 @@ namespace Avalonia.UnitTests public TestRoot() { Renderer = Mock.Of(); + LayoutManager = new LayoutManager(this); + IsVisible = true; } public TestRoot(IControl child) @@ -44,7 +46,7 @@ namespace Avalonia.UnitTests public double LayoutScaling { get; set; } = 1; - public ILayoutManager LayoutManager { get; set; } = new LayoutManager(); + public ILayoutManager LayoutManager { get; set; } public double RenderScaling => 1; diff --git a/tests/Avalonia.UnitTests/TestTemplatedRoot.cs b/tests/Avalonia.UnitTests/TestTemplatedRoot.cs index da4d92ce5e..38ab3c3c5d 100644 --- a/tests/Avalonia.UnitTests/TestTemplatedRoot.cs +++ b/tests/Avalonia.UnitTests/TestTemplatedRoot.cs @@ -16,6 +16,7 @@ namespace Avalonia.UnitTests public TestTemplatedRoot() { + LayoutManager = new LayoutManager(this); Template = new FuncControlTemplate((x, scope) => new ContentPresenter { Name = "PART_ContentPresenter", @@ -28,7 +29,7 @@ namespace Avalonia.UnitTests public double LayoutScaling => 1; - public ILayoutManager LayoutManager { get; set; } = new LayoutManager(); + public ILayoutManager LayoutManager { get; set; } public double RenderScaling => 1; From d3c3741bec5b08925bb8bb0ce325c78f7ed82ea3 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 23 Jun 2020 16:44:33 +0200 Subject: [PATCH 2/5] No longer need to pass root to ExecuteInitialLayoutPass. --- .../Embedding/EmbeddableControlRoot.cs | 2 +- .../Embedding/Offscreen/OffscreenTopLevel.cs | 2 +- src/Avalonia.Controls/Window.cs | 4 +-- src/Avalonia.Controls/WindowBase.cs | 2 +- .../Layout/ControlsBenchmark.cs | 2 +- tests/Avalonia.Benchmarks/Layout/Measure.cs | 2 +- .../Traversal/VisualTreeTraversal.cs | 2 +- .../Avalonia.Controls.UnitTests/GridTests.cs | 2 +- .../ListBoxTests.cs | 2 +- .../ItemsPresenterTests_Virtualization.cs | 2 +- ...emsPresenterTests_Virtualization_Simple.cs | 20 ++++++------ .../ScrollViewerTests.cs | 6 ++-- .../TopLevelTests.cs | 6 ++-- .../LayoutManagerTests.cs | 32 +++++++++---------- tests/Avalonia.LeakTests/ControlTests.cs | 20 ++++++------ .../Rendering/ImmediateRendererTests.cs | 8 ++--- .../Rendering/SceneGraph/SceneBuilderTests.cs | 6 ++-- .../SceneGraph/SceneBuilderTests_Layers.cs | 10 +++--- 18 files changed, 65 insertions(+), 65 deletions(-) diff --git a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs index 2d48a7d33b..c56294f9ec 100644 --- a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs +++ b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs @@ -28,7 +28,7 @@ namespace Avalonia.Controls.Embedding { EnsureInitialized(); ApplyTemplate(); - LayoutManager.ExecuteInitialLayoutPass(this); + LayoutManager.ExecuteInitialLayoutPass(); } private void EnsureInitialized() diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs index d326ab5734..b037dd9901 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs @@ -18,7 +18,7 @@ namespace Avalonia.Controls.Embedding.Offscreen { EnsureInitialized(); ApplyTemplate(); - LayoutManager.ExecuteInitialLayoutPass(this); + LayoutManager.ExecuteInitialLayoutPass(); } private void EnsureInitialized() diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index ff7cc41e3b..cedd20ace5 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -519,7 +519,7 @@ namespace Avalonia.Controls } } - LayoutManager.ExecuteInitialLayoutPass(this); + LayoutManager.ExecuteInitialLayoutPass(); using (BeginAutoSizing()) { @@ -592,7 +592,7 @@ namespace Avalonia.Controls } } - LayoutManager.ExecuteInitialLayoutPass(this); + LayoutManager.ExecuteInitialLayoutPass(); var result = new TaskCompletionSource(); diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index afc01db506..eb6e7319f5 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -162,7 +162,7 @@ namespace Avalonia.Controls if (!_hasExecutedInitialLayoutPass) { - LayoutManager.ExecuteInitialLayoutPass(this); + LayoutManager.ExecuteInitialLayoutPass(); _hasExecutedInitialLayoutPass = true; } PlatformImpl?.Show(); diff --git a/tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs b/tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs index de40c247e6..7170f6d7d4 100644 --- a/tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs +++ b/tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs @@ -24,7 +24,7 @@ namespace Avalonia.Benchmarks.Layout Renderer = new NullRenderer() }; - _root.LayoutManager.ExecuteInitialLayoutPass(_root); + _root.LayoutManager.ExecuteInitialLayoutPass(); } [Benchmark] diff --git a/tests/Avalonia.Benchmarks/Layout/Measure.cs b/tests/Avalonia.Benchmarks/Layout/Measure.cs index d03d17b4d3..fce2cddec9 100644 --- a/tests/Avalonia.Benchmarks/Layout/Measure.cs +++ b/tests/Avalonia.Benchmarks/Layout/Measure.cs @@ -25,7 +25,7 @@ namespace Avalonia.Benchmarks.Layout _controls.Add(panel); _controls = ControlHierarchyCreator.CreateChildren(_controls, panel, 3, 5, 5); - _root.LayoutManager.ExecuteInitialLayoutPass(_root); + _root.LayoutManager.ExecuteInitialLayoutPass(); } [Benchmark, MethodImpl(MethodImplOptions.NoInlining)] diff --git a/tests/Avalonia.Benchmarks/Traversal/VisualTreeTraversal.cs b/tests/Avalonia.Benchmarks/Traversal/VisualTreeTraversal.cs index fc2380d670..c6da3a941f 100644 --- a/tests/Avalonia.Benchmarks/Traversal/VisualTreeTraversal.cs +++ b/tests/Avalonia.Benchmarks/Traversal/VisualTreeTraversal.cs @@ -26,7 +26,7 @@ namespace Avalonia.Benchmarks.Traversal _shuffledControls = _controls.OrderBy(r => random.Next()).ToList(); - _root.LayoutManager.ExecuteInitialLayoutPass(_root); + _root.LayoutManager.ExecuteInitialLayoutPass(); } [Benchmark] diff --git a/tests/Avalonia.Controls.UnitTests/GridTests.cs b/tests/Avalonia.Controls.UnitTests/GridTests.cs index b3882c534b..4ffa526b85 100644 --- a/tests/Avalonia.Controls.UnitTests/GridTests.cs +++ b/tests/Avalonia.Controls.UnitTests/GridTests.cs @@ -1194,7 +1194,7 @@ namespace Avalonia.Controls.UnitTests Height = 50, }; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); PrintColumnDefinitions(grids[0]); Assert.Equal(5, grids[0].ColumnDefinitions[0].ActualWidth); diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index a7679ba388..c4346e571b 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -329,7 +329,7 @@ namespace Avalonia.Controls.UnitTests return tb; }, true); - lm.ExecuteInitialLayoutPass(wnd); + lm.ExecuteInitialLayoutPass(); target.Items = items; diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs index 3320ced8a4..d529cc4f75 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs @@ -232,7 +232,7 @@ namespace Avalonia.Controls.UnitTests.Presenters var scroll = (TestScroller)target.Parent; scroll.Width = scroll.Height = 100; - scroll.LayoutManager.ExecuteInitialLayoutPass(scroll); + scroll.LayoutManager.ExecuteInitialLayoutPass(); // Ensure than an intermediate measure pass doesn't add more controls than it // should. This can happen if target gets measured with Size.Infinity which diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs index d3fa565f4c..a467c6dd03 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs @@ -723,7 +723,7 @@ namespace Avalonia.Controls.UnitTests.Presenters var scroller = (TestScroller)target.Parent; scroller.Width = scroller.Height = 100; - scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); + scroller.LayoutManager.ExecuteInitialLayoutPass(); var last = (target.Items as IList)[10]; @@ -740,7 +740,7 @@ namespace Avalonia.Controls.UnitTests.Presenters var scroller = (TestScroller)target.Parent; scroller.Width = scroller.Height = 100; - scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); + scroller.LayoutManager.ExecuteInitialLayoutPass(); var last = (target.Items as IList)[10]; @@ -838,7 +838,7 @@ namespace Avalonia.Controls.UnitTests.Presenters var scroller = (TestScroller)target.Parent; scroller.Width = scroller.Height = 100; - scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); + scroller.LayoutManager.ExecuteInitialLayoutPass(); var from = target.Panel.Children[5]; var result = ((ILogicalScrollable)target).GetControlInDirection( @@ -855,7 +855,7 @@ namespace Avalonia.Controls.UnitTests.Presenters var scroller = (TestScroller)target.Parent; scroller.Width = scroller.Height = 100; - scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); + scroller.LayoutManager.ExecuteInitialLayoutPass(); var from = target.Panel.Children[9]; var result = ((ILogicalScrollable)target).GetControlInDirection( @@ -874,7 +874,7 @@ namespace Avalonia.Controls.UnitTests.Presenters scroller.Width = 100; scroller.Height = 95; - scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); + scroller.LayoutManager.ExecuteInitialLayoutPass(); var from = target.Panel.Children[8]; var result = ((ILogicalScrollable)target).GetControlInDirection( @@ -893,7 +893,7 @@ namespace Avalonia.Controls.UnitTests.Presenters scroller.Width = 100; scroller.Height = 95; - scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); + scroller.LayoutManager.ExecuteInitialLayoutPass(); ((ILogicalScrollable)target).Offset = new Vector(0, 11); var from = target.Panel.Children[1]; @@ -946,7 +946,7 @@ namespace Avalonia.Controls.UnitTests.Presenters var scroller = (TestScroller)target.Parent; scroller.Width = scroller.Height = 100; - scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); + scroller.LayoutManager.ExecuteInitialLayoutPass(); var from = target.Panel.Children[5]; var result = ((ILogicalScrollable)target).GetControlInDirection( @@ -963,7 +963,7 @@ namespace Avalonia.Controls.UnitTests.Presenters var scroller = (TestScroller)target.Parent; scroller.Width = scroller.Height = 100; - scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); + scroller.LayoutManager.ExecuteInitialLayoutPass(); var from = target.Panel.Children[9]; var result = ((ILogicalScrollable)target).GetControlInDirection( @@ -982,7 +982,7 @@ namespace Avalonia.Controls.UnitTests.Presenters scroller.Width = 95; scroller.Height = 100; - scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); + scroller.LayoutManager.ExecuteInitialLayoutPass(); var from = target.Panel.Children[8]; var result = ((ILogicalScrollable)target).GetControlInDirection( @@ -1001,7 +1001,7 @@ namespace Avalonia.Controls.UnitTests.Presenters scroller.Width = 95; scroller.Height = 100; - scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); + scroller.LayoutManager.ExecuteInitialLayoutPass(); ((ILogicalScrollable)target).Offset = new Vector(11, 0); var from = target.Panel.Children[1]; diff --git a/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs b/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs index deca3cfb75..ab21c5d330 100644 --- a/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs @@ -158,7 +158,7 @@ namespace Avalonia.Controls.UnitTests target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50)); target.Offset = new Vector(10, 10); - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); target.ScrollChanged += (s, e) => { @@ -188,7 +188,7 @@ namespace Avalonia.Controls.UnitTests target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50)); target.Offset = new Vector(10, 10); - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); target.ScrollChanged += (s, e) => { @@ -218,7 +218,7 @@ namespace Avalonia.Controls.UnitTests target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50)); target.Offset = new Vector(10, 10); - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); target.ScrollChanged += (s, e) => { diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index e5ff8d04de..ab272b261b 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -106,7 +106,7 @@ namespace Avalonia.Controls.UnitTests } }; - target.LayoutManager.ExecuteInitialLayoutPass(target); + target.LayoutManager.ExecuteInitialLayoutPass(); Assert.Equal(new Rect(0, 0, 321, 432), target.Bounds); } @@ -282,7 +282,7 @@ namespace Avalonia.Controls.UnitTests Content = child, }; - target.LayoutManager.ExecuteInitialLayoutPass(target); + target.LayoutManager.ExecuteInitialLayoutPass(); Assert.Equal(new Thickness(0), child.BorderThickness); @@ -295,7 +295,7 @@ namespace Avalonia.Controls.UnitTests }; Application.Current.Styles.Add(style); - target.LayoutManager.ExecuteInitialLayoutPass(target); + target.LayoutManager.ExecuteInitialLayoutPass(); Assert.Equal(new Thickness(2), child.BorderThickness); diff --git a/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs b/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs index 332e8a751d..392227d5fe 100644 --- a/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs +++ b/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs @@ -13,7 +13,7 @@ namespace Avalonia.Layout.UnitTests var control = new LayoutTestControl(); var root = new LayoutTestRoot { Child = control }; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); control.Measured = control.Arranged = false; control.InvalidateMeasure(); @@ -29,7 +29,7 @@ namespace Avalonia.Layout.UnitTests var control = new LayoutTestControl(); var root = new LayoutTestRoot { Child = control }; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); control.Measured = control.Arranged = false; control.InvalidateArrange(); @@ -45,7 +45,7 @@ namespace Avalonia.Layout.UnitTests var control = new LayoutTestControl(); var root = new LayoutTestRoot(); - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); root.Child = control; root.Measured = root.Arranged = false; @@ -80,7 +80,7 @@ namespace Avalonia.Layout.UnitTests root.DoMeasureOverride = MeasureOverride; control1.DoMeasureOverride = MeasureOverride; control2.DoMeasureOverride = MeasureOverride; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); control2.InvalidateMeasure(); control1.InvalidateMeasure(); @@ -115,7 +115,7 @@ namespace Avalonia.Layout.UnitTests root.DoMeasureOverride = MeasureOverride; control1.DoMeasureOverride = MeasureOverride; control2.DoMeasureOverride = MeasureOverride; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); control2.InvalidateMeasure(); root.InvalidateMeasure(); @@ -132,7 +132,7 @@ namespace Avalonia.Layout.UnitTests var control = new LayoutTestControl(); var root = new LayoutTestRoot { Child = control }; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); root.Measured = root.Arranged = false; control.Measured = control.Arranged = false; @@ -151,7 +151,7 @@ namespace Avalonia.Layout.UnitTests var control = new LayoutTestControl(); var root = new LayoutTestRoot { Child = control }; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); control.Measured = control.Arranged = false; control.InvalidateMeasure(); @@ -177,7 +177,7 @@ namespace Avalonia.Layout.UnitTests return new Size(100, 100); }; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); Assert.Equal(Size.Infinity, availableSize); } @@ -199,7 +199,7 @@ namespace Avalonia.Layout.UnitTests return s; }; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); Assert.Equal(new Size(100, 100), arrangeSize); root.Width = 120; @@ -225,7 +225,7 @@ namespace Avalonia.Layout.UnitTests } }; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); Assert.Equal(new Size(0, 0), root.DesiredSize); border.Width = 100; @@ -241,7 +241,7 @@ namespace Avalonia.Layout.UnitTests var control = new LayoutTestControl(); var root = new LayoutTestRoot { Child = control }; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); control.Measured = false; int cnt = 0; @@ -272,7 +272,7 @@ namespace Avalonia.Layout.UnitTests var control = new LayoutTestControl(); var root = new LayoutTestRoot { Child = control }; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); control.Arranged = false; int cnt = 0; @@ -313,7 +313,7 @@ namespace Avalonia.Layout.UnitTests panel.Children.AddRange(nonArrageableTargets); panel.Children.AddRange(targets); - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); foreach (var c in panel.Children.OfType()) { @@ -347,7 +347,7 @@ namespace Avalonia.Layout.UnitTests var control = new LayoutTestControl(); var root = new LayoutTestRoot { Child = control }; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); control.Measured = false; control.DoMeasureOverride = (l, s) => @@ -380,7 +380,7 @@ namespace Avalonia.Layout.UnitTests var root = new LayoutTestRoot { Child = control }; var count = 0; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); control.Measured = false; control.DoMeasureOverride = (l, s) => @@ -399,7 +399,7 @@ namespace Avalonia.Layout.UnitTests root.InvalidateMeasure(); control.InvalidateMeasure(); - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); Assert.Equal(new Size(200, 200), control.Bounds.Size); Assert.Equal(new Size(200, 200), control.DesiredSize); diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index 9bb9fd7145..f1e4c90947 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -44,7 +44,7 @@ namespace Avalonia.LeakTests window.Show(); // Do a layout and make sure that Canvas gets added to visual tree. - window.LayoutManager.ExecuteInitialLayoutPass(window); + window.LayoutManager.ExecuteInitialLayoutPass(); Assert.IsType(window.Presenter.Child); // Clear the content and ensure the Canvas is removed. @@ -82,7 +82,7 @@ namespace Avalonia.LeakTests window.Show(); // Do a layout and make sure that Canvas gets added to visual tree. - window.LayoutManager.ExecuteInitialLayoutPass(window); + window.LayoutManager.ExecuteInitialLayoutPass(); Assert.IsType(window.Find("foo")); Assert.IsType(window.Presenter.Child); @@ -122,7 +122,7 @@ namespace Avalonia.LeakTests // Do a layout and make sure that ScrollViewer gets added to visual tree and its // template applied. - window.LayoutManager.ExecuteInitialLayoutPass(window); + window.LayoutManager.ExecuteInitialLayoutPass(); Assert.IsType(window.Presenter.Child); Assert.IsType(((ScrollViewer)window.Presenter.Child).Presenter.Child); @@ -159,7 +159,7 @@ namespace Avalonia.LeakTests // Do a layout and make sure that TextBox gets added to visual tree and its // template applied. - window.LayoutManager.ExecuteInitialLayoutPass(window); + window.LayoutManager.ExecuteInitialLayoutPass(); Assert.IsType(window.Presenter.Child); Assert.NotEmpty(window.Presenter.Child.GetVisualChildren()); @@ -203,7 +203,7 @@ namespace Avalonia.LeakTests // Do a layout and make sure that TextBox gets added to visual tree and its // Text property set. - window.LayoutManager.ExecuteInitialLayoutPass(window); + window.LayoutManager.ExecuteInitialLayoutPass(); Assert.IsType(window.Presenter.Child); Assert.Equal("foo", ((TextBox)window.Presenter.Child).Text); @@ -241,7 +241,7 @@ namespace Avalonia.LeakTests // Do a layout and make sure that TextBox gets added to visual tree and its // template applied. - window.LayoutManager.ExecuteInitialLayoutPass(window); + window.LayoutManager.ExecuteInitialLayoutPass(); Assert.Same(textBox, window.Presenter.Child); // Get the border from the TextBox template. @@ -295,7 +295,7 @@ namespace Avalonia.LeakTests window.Show(); // Do a layout and make sure that TreeViewItems get realized. - window.LayoutManager.ExecuteInitialLayoutPass(window); + window.LayoutManager.ExecuteInitialLayoutPass(); Assert.Single(target.ItemContainerGenerator.Containers); // Clear the content and ensure the TreeView is removed. @@ -329,7 +329,7 @@ namespace Avalonia.LeakTests window.Show(); // Do a layout and make sure that Slider gets added to visual tree. - window.LayoutManager.ExecuteInitialLayoutPass(window); + window.LayoutManager.ExecuteInitialLayoutPass(); Assert.IsType(window.Presenter.Child); // Clear the content and ensure the Slider is removed. @@ -403,7 +403,7 @@ namespace Avalonia.LeakTests // Do a layout and make sure that Canvas gets added to visual tree with // its render transform. - window.LayoutManager.ExecuteInitialLayoutPass(window); + window.LayoutManager.ExecuteInitialLayoutPass(); var canvas = Assert.IsType(window.Presenter.Child); Assert.IsType(canvas.RenderTransform); @@ -512,7 +512,7 @@ namespace Avalonia.LeakTests window.Show(); - window.LayoutManager.ExecuteInitialLayoutPass(window); + window.LayoutManager.ExecuteInitialLayoutPass(); Assert.IsType(window.Presenter.Child); window.Content = null; diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs index 52552f0bee..acee9a50f5 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs @@ -134,7 +134,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering var root = new TestRoot(child); root.Renderer = new ImmediateRenderer(root); - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); root.Measure(new Size(50, 100)); root.Arrange(new Rect(new Size(50, 100))); @@ -171,7 +171,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering var root = new TestRoot(child); root.Renderer = new ImmediateRenderer(root); - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); root.Measure(new Size(300, 100)); root.Arrange(new Rect(new Size(300, 100))); @@ -222,7 +222,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering var root = new TestRoot(rootGrid); root.Renderer = new ImmediateRenderer(root); - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); var rootSize = new Size(RootWidth, RootHeight); root.Measure(rootSize); @@ -277,7 +277,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering var root = new TestRoot(rootGrid); root.Renderer = new ImmediateRenderer(root); - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); var rootSize = new Size(RootWidth, RootHeight); root.Measure(rootSize); diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs index b0f890b484..8527c3a95b 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs @@ -653,7 +653,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph }; var layout = tree.LayoutManager; - layout.ExecuteInitialLayoutPass(tree); + layout.ExecuteInitialLayoutPass(); var scene = new Scene(tree); var sceneBuilder = new SceneBuilder(); @@ -696,7 +696,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph }; var layout = tree.LayoutManager; - layout.ExecuteInitialLayoutPass(tree); + layout.ExecuteInitialLayoutPass(); var scene = new Scene(tree); var sceneBuilder = new SceneBuilder(); @@ -744,7 +744,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph }; var layout = tree.LayoutManager; - layout.ExecuteInitialLayoutPass(tree); + layout.ExecuteInitialLayoutPass(); var scene = new Scene(tree); var sceneBuilder = new SceneBuilder(); diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs index 1bece3ae22..e4d053d813 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs @@ -40,7 +40,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph }; var layout = tree.LayoutManager; - layout.ExecuteInitialLayoutPass(tree); + layout.ExecuteInitialLayoutPass(); var animation = new BehaviorSubject(0.5); border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); @@ -105,7 +105,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph }; var layout = tree.LayoutManager; - layout.ExecuteInitialLayoutPass(tree); + layout.ExecuteInitialLayoutPass(); var animation = new BehaviorSubject(0.5); border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); @@ -147,7 +147,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph }; var layout = tree.LayoutManager; - layout.ExecuteInitialLayoutPass(tree); + layout.ExecuteInitialLayoutPass(); var animation = new BehaviorSubject(0.5); border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); @@ -197,7 +197,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph }; var layout = tree.LayoutManager; - layout.ExecuteInitialLayoutPass(tree); + layout.ExecuteInitialLayoutPass(); var animation = new BehaviorSubject(0.5); border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); @@ -241,7 +241,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph }; var layout = tree.LayoutManager; - layout.ExecuteInitialLayoutPass(tree); + layout.ExecuteInitialLayoutPass(); var animation = new BehaviorSubject(0.5); border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); From 536a2f7c6220d549b4dbfdf20ec792080a39e541 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 23 Jun 2020 16:46:14 +0200 Subject: [PATCH 3/5] Check correct root in invalidation calls. --- src/Avalonia.Layout/LayoutManager.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index a81f2b61b7..8f964a6041 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/src/Avalonia.Layout/LayoutManager.cs @@ -43,6 +43,11 @@ namespace Avalonia.Layout #endif } + if (control.VisualRoot != _owner) + { + throw new ArgumentException("Attempt to call InvalidateMeasure on wrong LayoutManager."); + } + _toMeasure.Enqueue(control); _toArrange.Enqueue(control); QueueLayoutPass(); @@ -64,6 +69,11 @@ namespace Avalonia.Layout #endif } + if (control.VisualRoot != _owner) + { + throw new ArgumentException("Attempt to call InvalidateArrange on wrong LayoutManager."); + } + _toArrange.Enqueue(control); QueueLayoutPass(); } From 259529fbcde9307680c86bc0e8b984b4c3ccfe56 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 23 Jun 2020 16:57:41 +0200 Subject: [PATCH 4/5] Added unit test for not laying out invisible toplevel. --- .../LayoutManagerTests.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs b/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs index 392227d5fe..e429adce85 100644 --- a/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs +++ b/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs @@ -23,6 +23,22 @@ namespace Avalonia.Layout.UnitTests Assert.True(control.Arranged); } + [Fact] + public void Doesnt_Measure_And_Arrange_InvalidateMeasured_Control_When_TopLevel_Is_Not_Visible() + { + var control = new LayoutTestControl(); + var root = new LayoutTestRoot { Child = control, IsVisible = false }; + + root.LayoutManager.ExecuteInitialLayoutPass(); + control.Measured = control.Arranged = false; + + control.InvalidateMeasure(); + root.LayoutManager.ExecuteLayoutPass(); + + Assert.False(control.Measured); + Assert.False(control.Arranged); + } + [Fact] public void Arranges_InvalidateArranged_Control() { From 2807cbe6cb923982fa8faa0422caf97e21c43d90 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 24 Jun 2020 10:18:25 +0200 Subject: [PATCH 5/5] Make LayoutManager disposable. And dispose it on `TopLevel` close: this allows layout passes to be run before a window/popup is shown but prevents it being run after close. --- src/Avalonia.Controls/TopLevel.cs | 1 + src/Avalonia.Layout/ILayoutManager.cs | 2 +- src/Avalonia.Layout/LayoutManager.cs | 29 +++++++++++++++++-- src/Avalonia.Layout/LayoutQueue.cs | 9 +++++- .../TopLevelTests.cs | 17 +++++++++++ 5 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index c738a5ff3b..b4e33e6631 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -348,6 +348,7 @@ namespace Avalonia.Controls OnClosed(EventArgs.Empty); Renderer?.Dispose(); Renderer = null; + LayoutManager?.Dispose(); } /// diff --git a/src/Avalonia.Layout/ILayoutManager.cs b/src/Avalonia.Layout/ILayoutManager.cs index 688b6b83e5..d996b301f8 100644 --- a/src/Avalonia.Layout/ILayoutManager.cs +++ b/src/Avalonia.Layout/ILayoutManager.cs @@ -7,7 +7,7 @@ namespace Avalonia.Layout /// /// Manages measuring and arranging of controls. /// - public interface ILayoutManager + public interface ILayoutManager : IDisposable { /// /// Raised when the layout manager completes a layout pass. diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index 8f964a6041..c923aa62e9 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/src/Avalonia.Layout/LayoutManager.cs @@ -10,12 +10,13 @@ namespace Avalonia.Layout /// /// Manages measuring and arranging of controls. /// - public class LayoutManager : ILayoutManager + public class LayoutManager : ILayoutManager, IDisposable { private readonly ILayoutRoot _owner; private readonly LayoutQueue _toMeasure = new LayoutQueue(v => !v.IsMeasureValid); private readonly LayoutQueue _toArrange = new LayoutQueue(v => !v.IsArrangeValid); private readonly Action _executeLayoutPass; + private bool _disposed; private bool _queued; private bool _running; @@ -33,6 +34,11 @@ namespace Avalonia.Layout control = control ?? throw new ArgumentNullException(nameof(control)); Dispatcher.UIThread.VerifyAccess(); + if (_disposed) + { + return; + } + if (!control.IsAttachedToVisualTree) { #if DEBUG @@ -59,6 +65,11 @@ namespace Avalonia.Layout control = control ?? throw new ArgumentNullException(nameof(control)); Dispatcher.UIThread.VerifyAccess(); + if (_disposed) + { + return; + } + if (!control.IsAttachedToVisualTree) { #if DEBUG @@ -85,7 +96,7 @@ namespace Avalonia.Layout Dispatcher.UIThread.VerifyAccess(); - if (!_owner.IsVisible) + if (_disposed) { return; } @@ -150,6 +161,11 @@ namespace Avalonia.Layout /// public virtual void ExecuteInitialLayoutPass() { + if (_disposed) + { + return; + } + try { _running = true; @@ -179,6 +195,13 @@ namespace Avalonia.Layout ExecuteInitialLayoutPass(); } + public void Dispose() + { + _disposed = true; + _toMeasure.Dispose(); + _toArrange.Dispose(); + } + private void ExecuteMeasurePass() { while (_toMeasure.Count > 0) @@ -256,7 +279,7 @@ namespace Avalonia.Layout private void QueueLayoutPass() { - if (!_queued && !_running && _owner.IsVisible) + if (!_queued && !_running) { Dispatcher.UIThread.Post(_executeLayoutPass, DispatcherPriority.Layout); _queued = true; diff --git a/src/Avalonia.Layout/LayoutQueue.cs b/src/Avalonia.Layout/LayoutQueue.cs index e261a1b48e..1a9eb6b785 100644 --- a/src/Avalonia.Layout/LayoutQueue.cs +++ b/src/Avalonia.Layout/LayoutQueue.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; namespace Avalonia.Layout { - internal class LayoutQueue : IReadOnlyCollection + internal class LayoutQueue : IReadOnlyCollection, IDisposable { private struct Info { @@ -84,5 +84,12 @@ namespace Avalonia.Layout _notFinalizedBuffer.Clear(); } + + public void Dispose() + { + _inner.Clear(); + _loopQueueInfo.Clear(); + _notFinalizedBuffer.Clear(); + } } } diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index ab272b261b..e49e273bec 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -267,6 +267,23 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Close_Should_Dispose_LayoutManager() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var impl = new Mock(); + impl.SetupAllProperties(); + + var layoutManager = new Mock(); + var target = new TestTopLevel(impl.Object, layoutManager.Object); + + impl.Object.Closed(); + + layoutManager.Verify(x => x.Dispose()); + } + } + [Fact] public void Reacts_To_Changes_In_Global_Styles() {