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/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/VirtualizingStackPanel.cs b/src/Avalonia.Controls/VirtualizingStackPanel.cs
index 840ed06c73..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();
}
diff --git a/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs b/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs
index 820ce10629..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]