diff --git a/samples/VirtualizationTest/MainWindow.xaml b/samples/VirtualizationTest/MainWindow.xaml index 94a0fc1b68..681de0a815 100644 --- a/samples/VirtualizationTest/MainWindow.xaml +++ b/samples/VirtualizationTest/MainWindow.xaml @@ -5,6 +5,8 @@ Margin="16 0 0 0" MinWidth="150" Gap="4"> + @@ -28,7 +30,8 @@ + SelectionMode="Multiple" + VirtualizationMode="{Binding VirtualizationMode}"> diff --git a/samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs b/samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs index 0e1f56fa07..3e07aa54e9 100644 --- a/samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs +++ b/samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs @@ -2,8 +2,10 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Collections.Generic; using System.Linq; using Avalonia.Collections; +using Avalonia.Controls; using ReactiveUI; namespace VirtualizationTest.ViewModels @@ -15,6 +17,7 @@ namespace VirtualizationTest.ViewModels private int _newItemIndex; private IReactiveList _items; private string _prefix = "Item"; + private ItemVirtualizationMode _virtualizationMode = ItemVirtualizationMode.Simple; public MainWindowViewModel() { @@ -50,6 +53,15 @@ namespace VirtualizationTest.ViewModels private set { this.RaiseAndSetIfChanged(ref _items, value); } } + public ItemVirtualizationMode VirtualizationMode + { + get { return _virtualizationMode; } + set { this.RaiseAndSetIfChanged(ref _virtualizationMode, value); } + } + + public IEnumerable VirtualizationModes => + Enum.GetValues(typeof(ItemVirtualizationMode)).Cast(); + public ReactiveCommand AddItemCommand { get; private set; } public ReactiveCommand RecreateCommand { get; private set; } diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs index d690d4768b..c32e225bb0 100644 --- a/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs +++ b/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs @@ -14,8 +14,10 @@ namespace Avalonia.Controls.Presenters /// /// Base class for classes which handle virtualization for an . /// - internal abstract class ItemVirtualizer : IVirtualizingController + internal abstract class ItemVirtualizer : IVirtualizingController, IDisposable { + private bool disposedValue; + /// /// Initializes a new instance of the class. /// @@ -181,6 +183,14 @@ namespace Avalonia.Controls.Presenters return false; } + /// + public virtual void Dispose() + { + VirtualizingPanel.Controller = null; + VirtualizingPanel.Children.Clear(); + Owner.ItemContainerGenerator.Clear(); + } + /// /// Invalidates the current scroll. /// diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs index 21580a7cf2..d06d083220 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs @@ -32,6 +32,9 @@ namespace Avalonia.Controls.Presenters KeyboardNavigation.TabNavigationProperty.OverrideDefaultValue( typeof(ItemsPresenter), KeyboardNavigationMode.Once); + + VirtualizationModeProperty.Changed + .AddClassHandler(x => x.VirtualizationModeChanged); } /// @@ -113,5 +116,20 @@ namespace Avalonia.Controls.Presenters var maxY = Math.Max(scrollable.Extent.Height - scrollable.Viewport.Height, 0); return new Vector(Clamp(value.X, 0, maxX), Clamp(value.Y, 0, maxY)); } + + private void VirtualizationModeChanged(AvaloniaPropertyChangedEventArgs e) + { + _virtualizer?.Dispose(); + _virtualizer = ItemVirtualizer.Create(this); + + if (Items != null && Panel != null) + { + _virtualizer.ItemsChanged( + Items, + new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + ((ILogicalScrollable)this).InvalidateScroll?.Invoke(); + } } } \ No newline at end of file diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs index ef3297b5c3..5351231fec 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs @@ -59,6 +59,20 @@ namespace Avalonia.Controls.UnitTests.Presenters Assert.True(((ILogicalScrollable)target).IsLogicalScrollEnabled); } + [Fact] + public void Parent_ScrollContentPresenter_Properties_Should_Be_Set() + { + var target = CreateTarget(); + + target.ApplyTemplate(); + target.Measure(new Size(100, 100)); + target.Arrange(new Rect(0, 0, 100, 100)); + + var scroll = (ScrollContentPresenter)target.Parent; + Assert.Equal(new Size(0, 20), scroll.Extent); + Assert.Equal(new Size(0, 10), scroll.Viewport); + } + [Fact] public void Should_Fill_Panel_With_Containers() { @@ -138,6 +152,27 @@ namespace Avalonia.Controls.UnitTests.Presenters } } + [Fact] + public void Changing_VirtualizationMode_Simple_To_None_Should_Update_Scroll_Properties() + { + var target = CreateTarget(); + + target.ApplyTemplate(); + target.Measure(new Size(100, 100)); + target.Arrange(new Rect(0, 0, 100, 100)); + + var scroll = (ScrollContentPresenter)target.Parent; + Assert.Equal(10, target.Panel.Children.Count); + Assert.Equal(new Size(0, 20), scroll.Extent); + Assert.Equal(new Size(0, 10), scroll.Viewport); + + target.VirtualizationMode = ItemVirtualizationMode.None; + + Assert.Equal(20, target.Panel.Children.Count); + Assert.Equal(new Size(0, 200), scroll.Extent); + Assert.Equal(new Size(0, 100), scroll.Viewport); + } + private static ItemsPresenter CreateTarget( ItemVirtualizationMode mode = ItemVirtualizationMode.Simple, Orientation orientation = Orientation.Vertical,