From 35fe93c9c2971ed7e91399e02e6702dbd6c4a4b2 Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Tue, 31 May 2016 14:59:31 +0100 Subject: [PATCH 1/8] added support for add items and remove items in virtualization sample app. --- samples/VirtualizationTest/MainWindow.xaml | 3 +- .../ViewModels/MainWindowViewModel.cs | 37 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/samples/VirtualizationTest/MainWindow.xaml b/samples/VirtualizationTest/MainWindow.xaml index a6fd6918ef..988bd8bef5 100644 --- a/samples/VirtualizationTest/MainWindow.xaml +++ b/samples/VirtualizationTest/MainWindow.xaml @@ -21,10 +21,11 @@ UseFloatingWatermark="True" Text="{Binding NewItemString}"/> + - + diff --git a/samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs b/samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs index 9b0565401a..239eab2451 100644 --- a/samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs +++ b/samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs @@ -10,7 +10,9 @@ namespace VirtualizationTest.ViewModels internal class MainWindowViewModel : ReactiveObject { private int _itemCount = 200; + private string _newItemString; private IReactiveList _items; + private ItemViewModel _selectedItem; private string _prefix = "Item"; public MainWindowViewModel() @@ -18,6 +20,18 @@ namespace VirtualizationTest.ViewModels this.WhenAnyValue(x => x.ItemCount).Subscribe(ResizeItems); RecreateCommand = ReactiveCommand.Create(); RecreateCommand.Subscribe(_ => Recreate()); + + NewItemCommand = ReactiveCommand.Create(); + NewItemCommand.Subscribe(_ => Create()); + + RemoveItemCommand = ReactiveCommand.Create(); + RemoveItemCommand.Subscribe(_ => Remove()); + } + + public string NewItemString + { + get { return _newItemString; } + set { this.RaiseAndSetIfChanged(ref _newItemString, value); } } public int ItemCount @@ -26,14 +40,24 @@ namespace VirtualizationTest.ViewModels set { this.RaiseAndSetIfChanged(ref _itemCount, value); } } + public ItemViewModel SelectedItem + { + get { return _selectedItem; } + set { this.RaiseAndSetIfChanged(ref _selectedItem, value); } + } + public IReactiveList Items { get { return _items; } private set { this.RaiseAndSetIfChanged(ref _items, value); } } + public ReactiveCommand NewItemCommand { get; private set; } + public ReactiveCommand RecreateCommand { get; private set; } + public ReactiveCommand RemoveItemCommand { get; private set; } + private void ResizeItems(int count) { if (Items == null) @@ -54,6 +78,19 @@ namespace VirtualizationTest.ViewModels } } + private void Create() + { + Items.Add(new ItemViewModel(Items.Count, NewItemString)); + } + + private void Remove() + { + if (SelectedItem != null) + { + Items.Remove(SelectedItem); + } + } + private void Recreate() { _prefix = _prefix == "Item" ? "Recreated" : "Item"; From defae349a7589912b2278213fe67cd59f5c74284 Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Tue, 31 May 2016 15:07:31 +0100 Subject: [PATCH 2/8] added button to prepend and append items to list. --- samples/VirtualizationTest/MainWindow.xaml | 3 ++- .../ViewModels/MainWindowViewModel.cs | 18 ++++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/samples/VirtualizationTest/MainWindow.xaml b/samples/VirtualizationTest/MainWindow.xaml index 988bd8bef5..8595383bdf 100644 --- a/samples/VirtualizationTest/MainWindow.xaml +++ b/samples/VirtualizationTest/MainWindow.xaml @@ -20,7 +20,8 @@ - + + diff --git a/samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs b/samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs index 239eab2451..5b4a51f626 100644 --- a/samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs +++ b/samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs @@ -21,8 +21,11 @@ namespace VirtualizationTest.ViewModels RecreateCommand = ReactiveCommand.Create(); RecreateCommand.Subscribe(_ => Recreate()); - NewItemCommand = ReactiveCommand.Create(); - NewItemCommand.Subscribe(_ => Create()); + AppendItemCommand = ReactiveCommand.Create(); + AppendItemCommand.Subscribe(_ => Append()); + + PrePendItemCommand = ReactiveCommand.Create(); + PrePendItemCommand.Subscribe(_ => PrePend()); RemoveItemCommand = ReactiveCommand.Create(); RemoveItemCommand.Subscribe(_ => Remove()); @@ -52,7 +55,9 @@ namespace VirtualizationTest.ViewModels private set { this.RaiseAndSetIfChanged(ref _items, value); } } - public ReactiveCommand NewItemCommand { get; private set; } + public ReactiveCommand AppendItemCommand { get; private set; } + + public ReactiveCommand PrePendItemCommand { get; private set; } public ReactiveCommand RecreateCommand { get; private set; } @@ -78,11 +83,16 @@ namespace VirtualizationTest.ViewModels } } - private void Create() + private void Append() { Items.Add(new ItemViewModel(Items.Count, NewItemString)); } + private void PrePend () + { + Items.Insert(0, new ItemViewModel(0, NewItemString)); + } + private void Remove() { if (SelectedItem != null) From 94f8abbc4db4ee2ea7c1b3b387affbd68363e2c7 Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Tue, 31 May 2016 15:38:15 +0100 Subject: [PATCH 3/8] add items at selected index, or at end if no selection. --- samples/VirtualizationTest/MainWindow.xaml | 3 +-- .../ViewModels/MainWindowViewModel.cs | 25 ++++++++----------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/samples/VirtualizationTest/MainWindow.xaml b/samples/VirtualizationTest/MainWindow.xaml index 8595383bdf..aa882c052a 100644 --- a/samples/VirtualizationTest/MainWindow.xaml +++ b/samples/VirtualizationTest/MainWindow.xaml @@ -20,8 +20,7 @@ - - + diff --git a/samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs b/samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs index 5b4a51f626..dde3bd357d 100644 --- a/samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs +++ b/samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs @@ -21,11 +21,8 @@ namespace VirtualizationTest.ViewModels RecreateCommand = ReactiveCommand.Create(); RecreateCommand.Subscribe(_ => Recreate()); - AppendItemCommand = ReactiveCommand.Create(); - AppendItemCommand.Subscribe(_ => Append()); - - PrePendItemCommand = ReactiveCommand.Create(); - PrePendItemCommand.Subscribe(_ => PrePend()); + AddItemCommand = ReactiveCommand.Create(); + AddItemCommand.Subscribe(_ => AddItem()); RemoveItemCommand = ReactiveCommand.Create(); RemoveItemCommand.Subscribe(_ => Remove()); @@ -55,9 +52,7 @@ namespace VirtualizationTest.ViewModels private set { this.RaiseAndSetIfChanged(ref _items, value); } } - public ReactiveCommand AppendItemCommand { get; private set; } - - public ReactiveCommand PrePendItemCommand { get; private set; } + public ReactiveCommand AddItemCommand { get; private set; } public ReactiveCommand RecreateCommand { get; private set; } @@ -83,14 +78,16 @@ namespace VirtualizationTest.ViewModels } } - private void Append() + private void AddItem() { - Items.Add(new ItemViewModel(Items.Count, NewItemString)); - } + var index = Items.Count; - private void PrePend () - { - Items.Insert(0, new ItemViewModel(0, NewItemString)); + if (SelectedItem != null) + { + index = Items.IndexOf(SelectedItem); + } + + Items.Insert(index, new ItemViewModel(index, NewItemString)); } private void Remove() From e015fc15b267501b3f65d70291836993f9248183 Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Tue, 31 May 2016 16:03:29 +0100 Subject: [PATCH 4/8] add items, adds after selected item. --- samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs b/samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs index dde3bd357d..953f533065 100644 --- a/samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs +++ b/samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs @@ -84,7 +84,7 @@ namespace VirtualizationTest.ViewModels if (SelectedItem != null) { - index = Items.IndexOf(SelectedItem); + index = Items.IndexOf(SelectedItem) + 1; } Items.Insert(index, new ItemViewModel(index, NewItemString)); From e2fe94cafb3aa45d667e611cb44a21f06278b255 Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Tue, 31 May 2016 16:03:57 +0100 Subject: [PATCH 5/8] Added test for removing last item in the list, when scrolled to end. --- ...emsPresenterTests_Virtualization_Simple.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs index 036ecdce6f..32cd0c2825 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs @@ -181,6 +181,31 @@ namespace Avalonia.Controls.UnitTests.Presenters 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)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 { [Fact] From 2e9c19beaf650d746a3283464e653609e37bab81 Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Tue, 31 May 2016 17:12:52 +0100 Subject: [PATCH 6/8] Throw not implemented exception placeholder. --- .../Presenters/ItemVirtualizerSimple.cs | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs index 1ff85c50ac..b2c72174d3 100644 --- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs +++ b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs @@ -42,7 +42,7 @@ namespace Avalonia.Controls.Presenters if (panel.PixelOffset > 0) { panel.PixelOffset = 0; - delta += 1; + delta += 1; } if (delta != 0) @@ -87,6 +87,18 @@ namespace Avalonia.Controls.Presenters switch (e.Action) { + case NotifyCollectionChangedAction.Remove: + if (e.OldStartingIndex == ItemCount) + { + NextIndex = ItemCount - 1; + + throw new NotImplementedException("Remove the last item from the panel."); + } + + CreateRemoveContainers(); + RecycleContainers(); + break; + case NotifyCollectionChangedAction.Add: if (e.NewStartingIndex >= FirstIndex && e.NewStartingIndex + e.NewItems.Count < NextIndex) @@ -183,17 +195,25 @@ namespace Avalonia.Controls.Presenters foreach (var container in containers) { - var item = Items.ElementAt(itemIndex); - - if (!object.Equals(container.Item, item)) + if (itemIndex < ItemCount) { - 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; + } } From 1417aab362ed98b64bc48cfc450fc53c2aab2feb Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 1 Jun 2016 23:19:29 +0100 Subject: [PATCH 7/8] added some more test cases for simple virtualization. --- ...emsPresenterTests_Virtualization_Simple.cs | 58 ++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs index 32cd0c2825..36d43cd6fe 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs @@ -182,7 +182,63 @@ namespace Avalonia.Controls.UnitTests.Presenters } [Fact] - public void Removing_last_item_when_visible_should_UpdateContainers() + 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)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)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); From ac8185b8c3075d7f08675ca1f38b696722f9cab6 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 1 Jun 2016 23:24:46 +0100 Subject: [PATCH 8/8] added implementation for removing first and last elements. --- .../Presenters/ItemVirtualizerSimple.cs | 45 +++++++++++++++---- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs index b2c72174d3..5f32f5d10c 100644 --- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs +++ b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs @@ -88,15 +88,44 @@ namespace Avalonia.Controls.Presenters switch (e.Action) { case NotifyCollectionChangedAction.Remove: - if (e.OldStartingIndex == ItemCount) + if(e.OldStartingIndex >= FirstIndex && + e.OldStartingIndex + e.OldItems.Count <= NextIndex) { - NextIndex = ItemCount - 1; - - throw new NotImplementedException("Remove the last item from the panel."); - } - - CreateRemoveContainers(); - RecycleContainers(); + 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: