Browse Source

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.
Steven Kirk 5 years ago
parent
commit
2807cbe6cb

+ 1 - 0
src/Avalonia.Controls/TopLevel.cs

@@ -348,6 +348,7 @@ namespace Avalonia.Controls
             OnClosed(EventArgs.Empty);
             Renderer?.Dispose();
             Renderer = null;
+            LayoutManager?.Dispose();
         }
 
         /// <summary>

+ 1 - 1
src/Avalonia.Layout/ILayoutManager.cs

@@ -7,7 +7,7 @@ namespace Avalonia.Layout
     /// <summary>
     /// Manages measuring and arranging of controls.
     /// </summary>
-    public interface ILayoutManager
+    public interface ILayoutManager : IDisposable
     {
         /// <summary>
         /// Raised when the layout manager completes a layout pass.

+ 26 - 3
src/Avalonia.Layout/LayoutManager.cs

@@ -10,12 +10,13 @@ namespace Avalonia.Layout
     /// <summary>
     /// Manages measuring and arranging of controls.
     /// </summary>
-    public class LayoutManager : ILayoutManager
+    public class LayoutManager : ILayoutManager, IDisposable
     {
         private readonly ILayoutRoot _owner;
         private readonly LayoutQueue<ILayoutable> _toMeasure = new LayoutQueue<ILayoutable>(v => !v.IsMeasureValid);
         private readonly LayoutQueue<ILayoutable> _toArrange = new LayoutQueue<ILayoutable>(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
         /// <inheritdoc/>
         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;

+ 8 - 1
src/Avalonia.Layout/LayoutQueue.cs

@@ -4,7 +4,7 @@ using System.Collections.Generic;
 
 namespace Avalonia.Layout
 {
-    internal class LayoutQueue<T> : IReadOnlyCollection<T>
+    internal class LayoutQueue<T> : IReadOnlyCollection<T>, IDisposable
     {
         private struct Info
         {
@@ -84,5 +84,12 @@ namespace Avalonia.Layout
 
             _notFinalizedBuffer.Clear();
         }
+
+        public void Dispose()
+        {
+            _inner.Clear();
+            _loopQueueInfo.Clear();
+            _notFinalizedBuffer.Clear();
+        }
     }
 }

+ 17 - 0
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<ITopLevelImpl>();
+                impl.SetupAllProperties();
+
+                var layoutManager = new Mock<ILayoutManager>();
+                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()
         {