Browse Source

Merge pull request #554 from VitalElement/grokys/item-virtualization-avalonia

WIP Further Implementation of INCC handling
pull/558/head
Steven Kirk 10 years ago
parent
commit
96c5578690
  1. 5
      samples/VirtualizationTest/MainWindow.xaml
  2. 44
      samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs
  3. 61
      src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
  4. 81
      tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs

5
samples/VirtualizationTest/MainWindow.xaml

@ -20,11 +20,12 @@
<TextBox Watermark="Item to Create" <TextBox Watermark="Item to Create"
UseFloatingWatermark="True" UseFloatingWatermark="True"
Text="{Binding NewItemString}"/> Text="{Binding NewItemString}"/>
<Button Command="{Binding NewItemCommand}">Insert Item</Button> <Button Command="{Binding AddItemCommand}">Add Item</Button>
<Button Command="{Binding RemoveItemCommand}">Remove Item</Button>
<Button Command="{Binding RecreateCommand}">Recreate</Button> <Button Command="{Binding RecreateCommand}">Recreate</Button>
</StackPanel> </StackPanel>
<ListBox Name="listBox" Items="{Binding Items}"> <ListBox Name="listBox" Items="{Binding Items}" SelectedItem="{Binding SelectedItem}">
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
<DataTemplate> <DataTemplate>
<TextBlock Text="{Binding Header}"/> <TextBlock Text="{Binding Header}"/>

44
samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs

@ -10,7 +10,9 @@ namespace VirtualizationTest.ViewModels
internal class MainWindowViewModel : ReactiveObject internal class MainWindowViewModel : ReactiveObject
{ {
private int _itemCount = 200; private int _itemCount = 200;
private string _newItemString;
private IReactiveList<ItemViewModel> _items; private IReactiveList<ItemViewModel> _items;
private ItemViewModel _selectedItem;
private string _prefix = "Item"; private string _prefix = "Item";
public MainWindowViewModel() public MainWindowViewModel()
@ -18,6 +20,18 @@ namespace VirtualizationTest.ViewModels
this.WhenAnyValue(x => x.ItemCount).Subscribe(ResizeItems); this.WhenAnyValue(x => x.ItemCount).Subscribe(ResizeItems);
RecreateCommand = ReactiveCommand.Create(); RecreateCommand = ReactiveCommand.Create();
RecreateCommand.Subscribe(_ => Recreate()); RecreateCommand.Subscribe(_ => Recreate());
AddItemCommand = ReactiveCommand.Create();
AddItemCommand.Subscribe(_ => AddItem());
RemoveItemCommand = ReactiveCommand.Create();
RemoveItemCommand.Subscribe(_ => Remove());
}
public string NewItemString
{
get { return _newItemString; }
set { this.RaiseAndSetIfChanged(ref _newItemString, value); }
} }
public int ItemCount public int ItemCount
@ -26,14 +40,24 @@ namespace VirtualizationTest.ViewModels
set { this.RaiseAndSetIfChanged(ref _itemCount, value); } set { this.RaiseAndSetIfChanged(ref _itemCount, value); }
} }
public ItemViewModel SelectedItem
{
get { return _selectedItem; }
set { this.RaiseAndSetIfChanged(ref _selectedItem, value); }
}
public IReactiveList<ItemViewModel> Items public IReactiveList<ItemViewModel> Items
{ {
get { return _items; } get { return _items; }
private set { this.RaiseAndSetIfChanged(ref _items, value); } private set { this.RaiseAndSetIfChanged(ref _items, value); }
} }
public ReactiveCommand<object> AddItemCommand { get; private set; }
public ReactiveCommand<object> RecreateCommand { get; private set; } public ReactiveCommand<object> RecreateCommand { get; private set; }
public ReactiveCommand<object> RemoveItemCommand { get; private set; }
private void ResizeItems(int count) private void ResizeItems(int count)
{ {
if (Items == null) if (Items == null)
@ -54,6 +78,26 @@ namespace VirtualizationTest.ViewModels
} }
} }
private void AddItem()
{
var index = Items.Count;
if (SelectedItem != null)
{
index = Items.IndexOf(SelectedItem) + 1;
}
Items.Insert(index, new ItemViewModel(index, NewItemString));
}
private void Remove()
{
if (SelectedItem != null)
{
Items.Remove(SelectedItem);
}
}
private void Recreate() private void Recreate()
{ {
_prefix = _prefix == "Item" ? "Recreated" : "Item"; _prefix = _prefix == "Item" ? "Recreated" : "Item";

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

@ -42,7 +42,7 @@ namespace Avalonia.Controls.Presenters
if (panel.PixelOffset > 0) if (panel.PixelOffset > 0)
{ {
panel.PixelOffset = 0; panel.PixelOffset = 0;
delta += 1; delta += 1;
} }
if (delta != 0) if (delta != 0)
@ -87,6 +87,47 @@ namespace Avalonia.Controls.Presenters
switch (e.Action) switch (e.Action)
{ {
case NotifyCollectionChangedAction.Remove:
if(e.OldStartingIndex >= FirstIndex &&
e.OldStartingIndex + e.OldItems.Count <= NextIndex)
{
if (e.OldStartingIndex == FirstIndex)
{
// We are removing the first in the list.
VirtualizingPanel.Children.RemoveAt(e.OldStartingIndex - FirstIndex);
Owner.ItemContainerGenerator.Dematerialize(e.OldStartingIndex - FirstIndex, 1);
FirstIndex++; // This may not be necessary, but cant get to work without this.
// If all items are visible we need to reduce the NextIndex too.
if(NextIndex > ItemCount)
{
NextIndex = ItemCount;
}
CreateRemoveContainers();
RecycleContainers();
}
else if (e.OldStartingIndex + e.OldItems.Count == NextIndex)
{
// We are removing the last one in the list.
VirtualizingPanel.Children.RemoveAt(e.OldStartingIndex - FirstIndex);
Owner.ItemContainerGenerator.Dematerialize(e.OldStartingIndex - FirstIndex, 1);
NextIndex--;
}
else
{
// If all items are visible we need to reduce the NextIndex too.
if (NextIndex > ItemCount)
{
NextIndex = ItemCount;
}
CreateRemoveContainers();
RecycleContainers();
}
}
break;
case NotifyCollectionChangedAction.Add: case NotifyCollectionChangedAction.Add:
if (e.NewStartingIndex >= FirstIndex && if (e.NewStartingIndex >= FirstIndex &&
e.NewStartingIndex + e.NewItems.Count < NextIndex) e.NewStartingIndex + e.NewItems.Count < NextIndex)
@ -183,17 +224,25 @@ namespace Avalonia.Controls.Presenters
foreach (var container in containers) foreach (var container in containers)
{ {
var item = Items.ElementAt(itemIndex); if (itemIndex < ItemCount)
if (!object.Equals(container.Item, item))
{ {
if (!generator.TryRecycle(itemIndex, itemIndex, item, selector)) var item = Items.ElementAt(itemIndex);
if (!object.Equals(container.Item, item))
{ {
throw new NotImplementedException(); if (!generator.TryRecycle(itemIndex, itemIndex, item, selector))
{
throw new NotImplementedException();
}
} }
} }
else
{
panel.Children.RemoveAt(panel.Children.Count - 1);
}
++itemIndex; ++itemIndex;
} }
} }

81
tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs

@ -181,6 +181,87 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.Equal(expected, actual); Assert.Equal(expected, actual);
} }
[Fact]
public void Removing_First_Item_When_Visible_Should_UpdateContainers()
{
var target = CreateTarget(itemCount: 20);
target.ApplyTemplate();
target.Measure(new Size(100, 195));
target.Arrange(new Rect(0, 0, 100, 195));
((ILogicalScrollable)target).Offset = new Vector(0, 5);
var expected = Enumerable.Range(0, 20).Select(x => $"Item {x}").ToList();
var items = (ObservableCollection<string>)target.Items;
Assert.Equal(
expected,
target.Panel.Children.Select(x => x.DataContext));
items.Remove(items.First());
expected.Remove(expected.First());
var actual = target.Panel.Children.Select(x => x.DataContext).ToList();
Assert.Equal(expected, actual);
}
[Fact]
public void Removing_Items_From_Middle_Should_Update_Containers()
{
var target = CreateTarget(itemCount: 20);
target.ApplyTemplate();
target.Measure(new Size(100, 195));
target.Arrange(new Rect(0, 0, 100, 195));
((ILogicalScrollable)target).Offset = new Vector(0, 5);
var expected = Enumerable.Range(0, 20).Select(x => $"Item {x}").ToList();
var items = (ObservableCollection<string>)target.Items;
Assert.Equal(
expected,
target.Panel.Children.Select(x => x.DataContext));
items.RemoveAt(2);
expected.RemoveAt(2);
var actual = target.Panel.Children.Select(x => x.DataContext).ToList();
Assert.Equal(expected, actual);
items.RemoveAt(items.Count - 2);
expected.RemoveAt(expected.Count -2);
actual = target.Panel.Children.Select(x => x.DataContext).ToList();
Assert.Equal(expected, actual);
}
[Fact]
public void Removing_Last_Item_When_Visible_Should_UpdateContainers()
{
var target = CreateTarget(itemCount: 20);
target.ApplyTemplate();
target.Measure(new Size(100, 195));
target.Arrange(new Rect(0, 0, 100, 195));
((ILogicalScrollable)target).Offset = new Vector(0, 5);
var expected = Enumerable.Range(0, 20).Select(x => $"Item {x}").ToList();
var items = (ObservableCollection<string>)target.Items;
Assert.Equal(
expected,
target.Panel.Children.Select(x => x.DataContext));
items.Remove(items.Last());
expected.Remove(expected.Last());
var actual = target.Panel.Children.Select(x => x.DataContext).ToList();
Assert.Equal(expected, actual);
}
public class WithContainers public class WithContainers
{ {
[Fact] [Fact]

Loading…
Cancel
Save