diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj
index 22d17bf633..47a56fcc82 100644
--- a/src/Avalonia.Controls/Avalonia.Controls.csproj
+++ b/src/Avalonia.Controls/Avalonia.Controls.csproj
@@ -60,6 +60,7 @@
+
diff --git a/src/Avalonia.Controls/IVirtualizingController.cs b/src/Avalonia.Controls/IVirtualizingController.cs
new file mode 100644
index 0000000000..0b997f4948
--- /dev/null
+++ b/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
+{
+ ///
+ /// Interface implemented by controls that act as controllers for an
+ /// .
+ ///
+ public interface IVirtualizingController
+ {
+ ///
+ /// Called when the 's controls should be updated.
+ ///
+ ///
+ /// The controller should respond to this method being called by either adding
+ /// children up until becomes true or
+ /// removing controls.
+ ///
+ void UpdateControls();
+ }
+}
diff --git a/src/Avalonia.Controls/IVirtualizingPanel.cs b/src/Avalonia.Controls/IVirtualizingPanel.cs
index ce320c0da7..ca75517240 100644
--- a/src/Avalonia.Controls/IVirtualizingPanel.cs
+++ b/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
///
public interface IVirtualizingPanel : IPanel
{
+ ///
+ /// Gets or sets the controller for the virtualizing panel.
+ ///
+ ///
+ /// 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.
+ ///
+ IVirtualizingController Controller { get; set; }
+
///
/// Gets a value indicating whether the panel is full.
///
diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs
index dd7cf3bf1d..8f77cff32b 100644
--- a/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs
+++ b/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs
@@ -13,7 +13,7 @@ namespace Avalonia.Controls.Presenters
///
/// Base class for classes which handle virtualization for an .
///
- internal abstract class ItemVirtualizer
+ internal abstract class ItemVirtualizer : IVirtualizingController
{
///
/// Initializes a new instance of the 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);
+ }
- ///
- /// Called by the when it carries out an arrange.
- ///
- /// The final size passed to the arrange.
- public abstract void Arranging(Size finalSize);
+ if (virtualizingPanel != null)
+ {
+ virtualizingPanel.Controller = result;
+ }
- ///
- /// Called when a request is made to bring an item into view.
- ///
- /// The item to bring into view.
- /// The rect on the item to bring into view.
- /// True if the request was handled; otherwise false.
- public virtual bool BringIntoView(IVisual target, Rect targetRect)
+ return result;
+ }
+
+ ///
+ public virtual void UpdateControls()
{
- return false;
}
///
@@ -157,5 +157,16 @@ namespace Avalonia.Controls.Presenters
Items = items;
ItemCount = items.Count();
}
+
+ ///
+ /// Called when a request is made to bring an item into view.
+ ///
+ /// The item to bring into view.
+ /// The rect on the item to bring into view.
+ /// True if the request was handled; otherwise false.
+ public virtual bool BringIntoView(IVisual target, Rect targetRect)
+ {
+ return false;
+ }
}
}
diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerNone.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerNone.cs
index 303c7442c2..7450fdb2b4 100644
--- a/src/Avalonia.Controls/Presenters/ItemVirtualizerNone.cs
+++ b/src/Avalonia.Controls/Presenters/ItemVirtualizerNone.cs
@@ -52,12 +52,6 @@ namespace Avalonia.Controls.Presenters
get { throw new NotSupportedException(); }
}
- ///
- public override void Arranging(Size finalSize)
- {
- // We don't need to do anything here.
- }
-
///
public override void ItemsChanged(IEnumerable items, NotifyCollectionChangedEventArgs e)
{
diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
index 7750ed7f4a..78ac75e428 100644
--- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
+++ b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
@@ -84,7 +84,7 @@ namespace Avalonia.Controls.Presenters
}
///
- public override void Arranging(Size finalSize)
+ public override void UpdateControls()
{
CreateAndRemoveContainers();
((ILogicalScrollable)Owner).InvalidateScroll();
diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs
index 817d734506..b442c5bc61 100644
--- a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs
@@ -78,14 +78,6 @@ namespace Avalonia.Controls.Presenters
return _virtualizer?.BringIntoView(target, targetRect) ?? false;
}
- ///
- protected override Size ArrangeOverride(Size finalSize)
- {
- var result = base.ArrangeOverride(finalSize);
- _virtualizer.Arranging(finalSize);
- return result;
- }
-
///
protected override void PanelCreated(IPanel panel)
{
diff --git a/src/Avalonia.Controls/VirtualizingStackPanel.cs b/src/Avalonia.Controls/VirtualizingStackPanel.cs
index 2915ff07d5..77aae2548d 100644
--- a/src/Avalonia.Controls/VirtualizingStackPanel.cs
+++ b/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)
diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
index d1443fb2ae..db2bb0f886 100644
--- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
+++ b/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));
diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs
index 72ec2e6618..ef3297b5c3 100644
--- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs
+++ b/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()
{
diff --git a/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs b/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs
index 900f098198..d5217e007b 100644
--- a/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs
+++ b/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();
+
+ 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();
+
+ 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();