Browse Source

Make sure panel is remeasured after add/remove.

After items are added or removed then the virtualizing panel must be
remeasured. It's usually not remeasured if the available size is the
same as on the last measure as that causes an infinite loop; this adds a
`ForceInvalidateMeasure` to `IVirtualizingPanel` which will force a
remeasure even if the available size hasn't changed. Also adds tests for
the behavior.
pull/710/head
Steven Kirk 10 years ago
parent
commit
55d76d6cb9
  1. 19
      src/Avalonia.Controls/IVirtualizingPanel.cs
  2. 3
      src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
  3. 10
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  4. 48
      tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs

19
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
{
/// <summary>
@ -24,8 +26,8 @@ namespace Avalonia.Controls
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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.
/// </summary>
double PixelOffset { get; set; }
/// <summary>
/// Invalidates the measure of the control and forces a call to
/// <see cref="IVirtualizingController.UpdateControls"/> on the next measure.
/// </summary>
/// <remarks>
/// The implementation for this method should call
/// <see cref="ILayoutable.InvalidateMeasure"/> and also ensure that the next call to
/// <see cref="ILayoutable.Measure(Size)"/> calls
/// <see cref="IVirtualizingController.UpdateControls"/> on the next measure even if
/// the available size hasn't changed.
/// </remarks>
void ForceInvalidateMeasure();
}
}

3
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;
}
}

10
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();
}

48
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<IVirtualizingController>();
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<IVirtualizingController>();
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<IVirtualizingController>();
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]

Loading…
Cancel
Save