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.
pull/4163/head
Steven Kirk 6 years ago
parent
commit
2807cbe6cb
  1. 1
      src/Avalonia.Controls/TopLevel.cs
  2. 2
      src/Avalonia.Layout/ILayoutManager.cs
  3. 29
      src/Avalonia.Layout/LayoutManager.cs
  4. 9
      src/Avalonia.Layout/LayoutQueue.cs
  5. 17
      tests/Avalonia.Controls.UnitTests/TopLevelTests.cs

1
src/Avalonia.Controls/TopLevel.cs

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

2
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.

29
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;

9
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
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()
{

Loading…
Cancel
Save