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