diff --git a/src/Avalonia.Controls/IVirtualizingPanel.cs b/src/Avalonia.Controls/IVirtualizingPanel.cs index 5d35fa1ec8..792dee8ae8 100644 --- a/src/Avalonia.Controls/IVirtualizingPanel.cs +++ b/src/Avalonia.Controls/IVirtualizingPanel.cs @@ -1,6 +1,8 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Layout; + namespace Avalonia.Controls { /// @@ -24,8 +26,8 @@ namespace Avalonia.Controls /// /// /// This property should return false until enough children are added to fill the space - /// passed into the last measure in the direction of scroll. It should be updated - /// immediately after a child is added or removed. + /// passed into the last measure or arrange in the direction of scroll. It should be + /// updated immediately after a child is added or removed. /// bool IsFull { get; } @@ -63,5 +65,18 @@ namespace Avalonia.Controls /// Gets or sets the current pixel offset of the items in the direction of scroll. /// double PixelOffset { get; set; } + + /// + /// Invalidates the measure of the control and forces a call to + /// on the next measure. + /// + /// + /// The implementation for this method should call + /// and also ensure that the next call to + /// calls + /// on the next measure even if + /// the available size hasn't changed. + /// + void ForceInvalidateMeasure(); } } diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index f5d8344bda..b06c4b9695 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -22,6 +22,11 @@ namespace Avalonia.Platform /// Size ClientSize { get; set; } + /// + /// Gets the maximum size of a window on the system. + /// + Size MaxClientSize { get; } + /// /// Gets the scaling factor for the window. /// diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index 05a51d02aa..609e9834cb 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -11,11 +11,6 @@ namespace Avalonia.Platform /// public interface IWindowImpl : ITopLevelImpl { - /// - /// Gets the maximum size of a window on the system. - /// - Size MaxClientSize { get; } - /// /// Gets or sets the minimized/maximized state of the window. /// diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs index 83a0313dd5..d2e5a720b7 100644 --- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs +++ b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs @@ -121,6 +121,7 @@ namespace Avalonia.Controls.Presenters RecycleContainers(); } + panel.ForceInvalidateMeasure(); break; case NotifyCollectionChangedAction.Remove: @@ -130,6 +131,7 @@ namespace Avalonia.Controls.Presenters RecycleContainersOnRemove(); } + panel.ForceInvalidateMeasure(); break; case NotifyCollectionChangedAction.Move: @@ -140,6 +142,7 @@ namespace Avalonia.Controls.Presenters case NotifyCollectionChangedAction.Reset: RecycleContainersOnRemove(); CreateAndRemoveContainers(); + panel.ForceInvalidateMeasure(); break; } } diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs index 2e586a25e3..fa15766e4d 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs @@ -99,7 +99,7 @@ namespace Avalonia.Controls.Presenters // the available size. if (availableSize == Size.Infinity && VirtualizationMode != ItemVirtualizationMode.None) { - var window = VisualRoot as Window; + var window = VisualRoot as TopLevel; if (window != null) { diff --git a/src/Avalonia.Controls/VirtualizingStackPanel.cs b/src/Avalonia.Controls/VirtualizingStackPanel.cs index 6daad0b90e..2e5afaf170 100644 --- a/src/Avalonia.Controls/VirtualizingStackPanel.cs +++ b/src/Avalonia.Controls/VirtualizingStackPanel.cs @@ -19,6 +19,7 @@ namespace Avalonia.Controls private double _averageItemSize; private int _averageCount; private double _pixelOffset; + private bool _forceRemeasure; bool IVirtualizingPanel.IsFull { @@ -61,10 +62,17 @@ namespace Avalonia.Controls private IVirtualizingController Controller => ((IVirtualizingPanel)this).Controller; + void IVirtualizingPanel.ForceInvalidateMeasure() + { + InvalidateMeasure(); + _forceRemeasure = true; + } + protected override Size MeasureOverride(Size availableSize) { - if (availableSize != ((ILayoutable)this).PreviousMeasure) + if (_forceRemeasure || availableSize != ((ILayoutable)this).PreviousMeasure) { + _forceRemeasure = false; _availableSpace = availableSize; Controller?.UpdateControls(); } @@ -74,6 +82,7 @@ namespace Avalonia.Controls protected override Size ArrangeOverride(Size finalSize) { + _availableSpace = finalSize; _canBeRemoved = 0; _takenSpace = 0; _averageItemSize = 0; diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs index 02c415d068..2a59227b52 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs @@ -658,6 +658,7 @@ namespace Avalonia.Controls.UnitTests.Presenters Assert.Equal(expected, actual); } + [Fact] public void Should_Add_Containers_For_Items_After_Clear() { var target = CreateTarget(itemCount: 10); diff --git a/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs b/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs index b8cd868252..b0ae3df8a2 100644 --- a/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs @@ -22,7 +22,51 @@ namespace Avalonia.Controls.UnitTests target.Controller = controller.Object; target.Measure(new Size(100, 100)); - controller.Verify(x => x.UpdateControls()); + controller.Verify(x => x.UpdateControls(), Times.Once()); + } + + [Fact] + public void Measure_Invokes_Controller_UpdateControls_If_AvailableSize_Changes() + { + var target = (IVirtualizingPanel)new VirtualizingStackPanel(); + var controller = new Mock(); + + target.Controller = controller.Object; + target.Measure(new Size(100, 100)); + target.InvalidateMeasure(); + target.Measure(new Size(100, 100)); + target.InvalidateMeasure(); + target.Measure(new Size(100, 101)); + + controller.Verify(x => x.UpdateControls(), Times.Exactly(2)); + } + + [Fact] + public void Measure_Does_Not_Invoke_Controller_UpdateControls_If_AvailableSize_Is_The_Same() + { + var target = (IVirtualizingPanel)new VirtualizingStackPanel(); + var controller = new Mock(); + + target.Controller = controller.Object; + target.Measure(new Size(100, 100)); + target.InvalidateMeasure(); + target.Measure(new Size(100, 100)); + + controller.Verify(x => x.UpdateControls(), Times.Once()); + } + + [Fact] + public void Measure_Invokes_Controller_UpdateControls_If_AvailableSize_Is_The_Same_After_ForceInvalidateMeasure() + { + var target = (IVirtualizingPanel)new VirtualizingStackPanel(); + var controller = new Mock(); + + target.Controller = controller.Object; + target.Measure(new Size(100, 100)); + target.ForceInvalidateMeasure(); + target.Measure(new Size(100, 100)); + + controller.Verify(x => x.UpdateControls(), Times.Exactly(2)); } [Fact] @@ -35,7 +79,7 @@ namespace Avalonia.Controls.UnitTests target.Measure(new Size(100, 100)); target.Arrange(new Rect(0, 0, 110, 110)); - controller.Verify(x => x.UpdateControls()); + controller.Verify(x => x.UpdateControls(), Times.Exactly(2)); } [Fact] @@ -118,6 +162,20 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(2, target.PixelOverflow); } + [Fact] + public void Reports_PixelOverflow_After_Arrange_Smaller_Than_Measure() + { + var target = (IVirtualizingPanel)new VirtualizingStackPanel(); + + target.Children.Add(new Canvas { Width = 50, Height = 50 }); + target.Children.Add(new Canvas { Width = 50, Height = 52 }); + + target.Measure(new Size(100, 100)); + target.Arrange(new Rect(0, 0, 50, 50)); + + Assert.Equal(52, target.PixelOverflow); + } + [Fact] public void Reports_PixelOverflow_With_PixelOffset() {