Browse Source

Fix virtualized list to taking up available space.

Previous commit was slightly wrong: too many controls were being created
for the list in VirtualizingTest.
pull/545/head
Steven Kirk 10 years ago
parent
commit
f660da41c7
  1. 1
      src/Avalonia.Controls/Avalonia.Controls.csproj
  2. 22
      src/Avalonia.Controls/IVirtualizingController.cs
  3. 12
      src/Avalonia.Controls/IVirtualizingPanel.cs
  4. 45
      src/Avalonia.Controls/Presenters/ItemVirtualizer.cs
  5. 6
      src/Avalonia.Controls/Presenters/ItemVirtualizerNone.cs
  6. 2
      src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
  7. 8
      src/Avalonia.Controls/Presenters/ItemsPresenter.cs
  8. 38
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  9. 2
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  10. 18
      tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs
  11. 32
      tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs

1
src/Avalonia.Controls/Avalonia.Controls.csproj

@ -60,6 +60,7 @@
<Compile Include="DropDownItem.cs" />
<Compile Include="ISetInheritanceParent.cs" />
<Compile Include="ItemVirtualizationMode.cs" />
<Compile Include="IVirtualizingController.cs" />
<Compile Include="IVirtualizingPanel.cs" />
<Compile Include="LayoutTransformControl.cs" />
<Compile Include="Mixins\ContentControlMixin.cs" />

22
src/Avalonia.Controls/IVirtualizingController.cs

@ -0,0 +1,22 @@
// 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.
namespace Avalonia.Controls
{
/// <summary>
/// Interface implemented by controls that act as controllers for an
/// <see cref="IVirtualizingPanel"/>.
/// </summary>
public interface IVirtualizingController
{
/// <summary>
/// Called when the <see cref="IVirtualizingPanel"/>'s controls should be updated.
/// </summary>
/// <remarks>
/// The controller should respond to this method being called by either adding
/// children up until <see cref="IVirtualizingPanel.IsFull"/> becomes true or
/// removing <see cref="IVirtualizingPanel.OverflowCount"/> controls.
/// </remarks>
void UpdateControls();
}
}

12
src/Avalonia.Controls/IVirtualizingPanel.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Controls.Primitives;
namespace Avalonia.Controls
{
@ -10,6 +11,17 @@ namespace Avalonia.Controls
/// </summary>
public interface IVirtualizingPanel : IPanel
{
/// <summary>
/// Gets or sets the controller for the virtualizing panel.
/// </summary>
/// <remarks>
/// A virtualizing controller is responsible for maintaing the controls in the virtualizing
/// panel. This property will be set by the controller when virtualization is initialized.
/// Note that this property may remain null if the panel is added to a control that does
/// not act as a virtualizing controller.
/// </remarks>
IVirtualizingController Controller { get; set; }
/// <summary>
/// Gets a value indicating whether the panel is full.
/// </summary>

45
src/Avalonia.Controls/Presenters/ItemVirtualizer.cs

@ -13,7 +13,7 @@ namespace Avalonia.Controls.Presenters
/// <summary>
/// Base class for classes which handle virtualization for an <see cref="ItemsPresenter"/>.
/// </summary>
internal abstract class ItemVirtualizer
internal abstract class ItemVirtualizer : IVirtualizingController
{
/// <summary>
/// Initializes a new instance of the <see cref="ItemVirtualizer"/> class.
@ -115,34 +115,34 @@ namespace Avalonia.Controls.Presenters
{
var virtualizingPanel = owner.Panel as IVirtualizingPanel;
var scrollable = (ILogicalScrollable)owner;
ItemVirtualizer result = null;
if (virtualizingPanel != null && scrollable.InvalidateScroll != null)
{
switch (owner.VirtualizationMode)
{
case ItemVirtualizationMode.Simple:
return new ItemVirtualizerSimple(owner);
result = new ItemVirtualizerSimple(owner);
break;
}
}
return new ItemVirtualizerNone(owner);
}
if (result == null)
{
result = new ItemVirtualizerNone(owner);
}
/// <summary>
/// Called by the <see cref="Owner"/> when it carries out an arrange.
/// </summary>
/// <param name="finalSize">The final size passed to the arrange.</param>
public abstract void Arranging(Size finalSize);
if (virtualizingPanel != null)
{
virtualizingPanel.Controller = result;
}
/// <summary>
/// Called when a request is made to bring an item into view.
/// </summary>
/// <param name="target">The item to bring into view.</param>
/// <param name="targetRect">The rect on the item to bring into view.</param>
/// <returns>True if the request was handled; otherwise false.</returns>
public virtual bool BringIntoView(IVisual target, Rect targetRect)
return result;
}
/// <inheritdoc/>
public virtual void UpdateControls()
{
return false;
}
/// <summary>
@ -157,5 +157,16 @@ namespace Avalonia.Controls.Presenters
Items = items;
ItemCount = items.Count();
}
/// <summary>
/// Called when a request is made to bring an item into view.
/// </summary>
/// <param name="target">The item to bring into view.</param>
/// <param name="targetRect">The rect on the item to bring into view.</param>
/// <returns>True if the request was handled; otherwise false.</returns>
public virtual bool BringIntoView(IVisual target, Rect targetRect)
{
return false;
}
}
}

6
src/Avalonia.Controls/Presenters/ItemVirtualizerNone.cs

@ -52,12 +52,6 @@ namespace Avalonia.Controls.Presenters
get { throw new NotSupportedException(); }
}
/// <inheritdoc/>
public override void Arranging(Size finalSize)
{
// We don't need to do anything here.
}
/// <inheritdoc/>
public override void ItemsChanged(IEnumerable items, NotifyCollectionChangedEventArgs e)
{

2
src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs

@ -84,7 +84,7 @@ namespace Avalonia.Controls.Presenters
}
/// <inheritdoc/>
public override void Arranging(Size finalSize)
public override void UpdateControls()
{
CreateAndRemoveContainers();
((ILogicalScrollable)Owner).InvalidateScroll();

8
src/Avalonia.Controls/Presenters/ItemsPresenter.cs

@ -78,14 +78,6 @@ namespace Avalonia.Controls.Presenters
return _virtualizer?.BringIntoView(target, targetRect) ?? false;
}
/// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize)
{
var result = base.ArrangeOverride(finalSize);
_virtualizer.Arranging(finalSize);
return result;
}
/// <inheritdoc/>
protected override void PanelCreated(IPanel panel)
{

38
src/Avalonia.Controls/VirtualizingStackPanel.cs

@ -4,11 +4,13 @@
using System;
using System.Collections.Specialized;
using Avalonia.Layout;
using Avalonia.VisualTree;
namespace Avalonia.Controls
{
public class VirtualizingStackPanel : StackPanel, IVirtualizingPanel
{
private Size _availableSpace;
private double _takenSpace;
private int _canBeRemoved;
private double _averageItemSize;
@ -20,15 +22,14 @@ namespace Avalonia.Controls
get
{
return Orientation == Orientation.Horizontal ?
_takenSpace >= AvailableSpace.Width :
_takenSpace >= AvailableSpace.Height;
_takenSpace >= _availableSpace.Width :
_takenSpace >= _availableSpace.Height;
}
}
IVirtualizingController IVirtualizingPanel.Controller { get; set; }
int IVirtualizingPanel.OverflowCount => _canBeRemoved;
Orientation IVirtualizingPanel.ScrollDirection => Orientation;
double IVirtualizingPanel.AverageItemSize => _averageItemSize;
double IVirtualizingPanel.PixelOverflow
@ -55,16 +56,30 @@ namespace Avalonia.Controls
}
}
// TODO: We need to put a reasonable limit on this, probably based on the max window size.
private Size AvailableSpace => ((ILayoutable)this).PreviousMeasure ?? Bounds.Size;
private IVirtualizingController Controller => ((IVirtualizingPanel)this).Controller;
protected override Size MeasureOverride(Size availableSize)
{
if (availableSize != ((ILayoutable)this).PreviousMeasure)
{
// TODO: We need to put a reasonable limit on this, probably based on the max
// window size.
_availableSpace = availableSize;
Controller?.UpdateControls();
}
return base.MeasureOverride(availableSize);
}
protected override Size ArrangeOverride(Size finalSize)
{
_availableSpace = finalSize;
_canBeRemoved = 0;
_takenSpace = 0;
_averageItemSize = 0;
_averageCount = 0;
var result = base.ArrangeOverride(finalSize);
Controller?.UpdateControls();
return result;
}
@ -103,7 +118,7 @@ namespace Avalonia.Controls
rect = new Rect(rect.X, rect.Y - _pixelOffset, rect.Width, rect.Height);
child.Arrange(rect);
if (rect.Y >= AvailableSpace.Height)
if (rect.Y >= _availableSpace.Height)
{
++_canBeRemoved;
}
@ -120,7 +135,7 @@ namespace Avalonia.Controls
rect = new Rect(rect.X - _pixelOffset, rect.Y, rect.Width, rect.Height);
child.Arrange(rect);
if (rect.X >= AvailableSpace.Width)
if (rect.X >= _availableSpace.Width)
{
++_canBeRemoved;
}
@ -139,7 +154,7 @@ namespace Avalonia.Controls
var bounds = Bounds;
var gap = Gap;
child.Measure(AvailableSpace);
child.Measure(_availableSpace);
++_averageCount;
if (Orientation == Orientation.Vertical)
@ -173,6 +188,11 @@ namespace Avalonia.Controls
_takenSpace -= width + gap;
RemoveFromAverageItemSize(width);
}
if (_canBeRemoved > 0)
{
--_canBeRemoved;
}
}
private void AddToAverageItemSize(double value)

2
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@ -224,7 +224,7 @@ namespace Avalonia.Controls.UnitTests
// and re-arrange everything.
foreach (IControl i in target.GetSelfAndVisualDescendents())
{
i.InvalidateArrange();
i.InvalidateMeasure();
}
target.Arrange(new Rect(0, 0, 100, 100));

18
tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs

@ -65,9 +65,11 @@ namespace Avalonia.Controls.UnitTests.Presenters
var target = CreateTarget();
target.ApplyTemplate();
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(10, target.Panel.Children.Count);
target.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(10, target.Panel.Children.Count);
}
@ -83,6 +85,20 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.Equal(2, target.Panel.Children.Count);
}
[Fact]
public void Should_Expand_To_Fit_Containers_When_Flexible_Size()
{
var target = CreateTarget();
target.ApplyTemplate();
target.Measure(Size.Infinity);
target.Arrange(new Rect(target.DesiredSize));
Assert.Equal(new Size(10, 200), target.DesiredSize);
Assert.Equal(new Size(10, 200), target.Bounds.Size);
Assert.Equal(20, target.Panel.Children.Count);
}
[Fact]
public void Initial_Item_DataContexts_Should_Be_Correct()
{

32
tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs

@ -1,6 +1,7 @@
// 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 Moq;
using Xunit;
namespace Avalonia.Controls.UnitTests
@ -9,25 +10,52 @@ namespace Avalonia.Controls.UnitTests
{
public class Vertical
{
[Fact]
public void Measure_Invokes_Controller_UpdateControls()
{
var target = (IVirtualizingPanel)new VirtualizingStackPanel();
var controller = new Mock<IVirtualizingController>();
target.Controller = controller.Object;
target.Measure(new Size(100, 100));
controller.Verify(x => x.UpdateControls());
}
[Fact]
public void Arrange_Invokes_Controller_UpdateControls()
{
var target = (IVirtualizingPanel)new VirtualizingStackPanel();
var controller = new Mock<IVirtualizingController>();
target.Controller = controller.Object;
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 110, 110));
controller.Verify(x => x.UpdateControls());
}
[Fact]
public void Reports_IsFull_False_Until_Measure_Height_Is_Reached()
{
var target = (IVirtualizingPanel)new VirtualizingStackPanel();
target.Measure(new Size(100, 100));
target.Arrange(new Rect(target.DesiredSize));
Assert.Equal(new Size(0, 0), target.Bounds.Size);
Assert.False(target.IsFull);
Assert.Equal(0, target.OverflowCount);
target.Children.Add(new Canvas { Width = 50, Height = 50 });
Assert.False(target.IsFull);
Assert.Equal(0, target.OverflowCount);
target.Children.Add(new Canvas { Width = 50, Height = 50 });
Assert.True(target.IsFull);
Assert.Equal(0, target.OverflowCount);
}
[Fact]
public void Reports_Overflow_Only_After_Arrange()
public void Reports_Overflow_After_Arrange()
{
var target = (IVirtualizingPanel)new VirtualizingStackPanel();

Loading…
Cancel
Save