diff --git a/samples/VirtualizationDemo/MainWindow.xaml b/samples/VirtualizationDemo/MainWindow.xaml index 58970eff01..12137cd03d 100644 --- a/samples/VirtualizationDemo/MainWindow.xaml +++ b/samples/VirtualizationDemo/MainWindow.xaml @@ -39,6 +39,8 @@ + + - + diff --git a/samples/VirtualizationDemo/ViewModels/ItemViewModel.cs b/samples/VirtualizationDemo/ViewModels/ItemViewModel.cs index e883cdfeb9..4401a2dfeb 100644 --- a/samples/VirtualizationDemo/ViewModels/ItemViewModel.cs +++ b/samples/VirtualizationDemo/ViewModels/ItemViewModel.cs @@ -10,6 +10,7 @@ namespace VirtualizationDemo.ViewModels { private string _prefix; private int _index; + private double _height = double.NaN; public ItemViewModel(int index, string prefix = "Item") { @@ -18,5 +19,11 @@ namespace VirtualizationDemo.ViewModels } public string Header => $"{_prefix} {_index}"; + + public double Height + { + get => _height; + set => this.RaiseAndSetIfChanged(ref _height, value); + } } } diff --git a/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs b/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs index eba17f92e4..80e0fb2586 100644 --- a/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs +++ b/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs @@ -98,6 +98,24 @@ namespace VirtualizationDemo.ViewModels public ReactiveCommand SelectFirstCommand { get; private set; } public ReactiveCommand SelectLastCommand { get; private set; } + public void RandomizeSize() + { + var random = new Random(); + + foreach (var i in Items) + { + i.Height = random.Next(240) + 10; + } + } + + public void ResetSize() + { + foreach (var i in Items) + { + i.Height = double.NaN; + } + } + private void ResizeItems(int count) { if (Items == null) diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs index f72a65ead2..d11ce9a7ea 100644 --- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs +++ b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs @@ -20,6 +20,8 @@ namespace Avalonia.Controls.Presenters /// internal class ItemVirtualizerSimple : ItemVirtualizer { + private int _anchor; + /// /// Initializes a new instance of the class. /// @@ -362,7 +364,10 @@ namespace Avalonia.Controls.Presenters if (panel.OverflowCount > 0) { - RemoveContainers(panel.OverflowCount); + if (_anchor <= FirstIndex) + { + RemoveContainers(panel.OverflowCount); + } } } @@ -540,7 +545,9 @@ namespace Avalonia.Controls.Presenters // it means we're running a unit test. if (container != null && layoutManager != null) { + _anchor = index; layoutManager.ExecuteLayoutPass(); + _anchor = -1; if (newOffset != -1 && newOffset != OffsetValue) { diff --git a/src/Avalonia.Controls/Primitives/RangeBase.cs b/src/Avalonia.Controls/Primitives/RangeBase.cs index 76df94cdb8..ae175734b9 100644 --- a/src/Avalonia.Controls/Primitives/RangeBase.cs +++ b/src/Avalonia.Controls/Primitives/RangeBase.cs @@ -44,13 +44,13 @@ namespace Avalonia.Controls.Primitives /// Defines the property. /// public static readonly StyledProperty SmallChangeProperty = - AvaloniaProperty.Register(nameof(SmallChange), 0.1); + AvaloniaProperty.Register(nameof(SmallChange), 1); /// /// Defines the property. /// public static readonly StyledProperty LargeChangeProperty = - AvaloniaProperty.Register(nameof(LargeChange), 1); + AvaloniaProperty.Register(nameof(LargeChange), 10); private double _minimum; private double _maximum = 100.0; diff --git a/src/Avalonia.Controls/Primitives/ScrollBar.cs b/src/Avalonia.Controls/Primitives/ScrollBar.cs index f0d8c81808..e1b3061b54 100644 --- a/src/Avalonia.Controls/Primitives/ScrollBar.cs +++ b/src/Avalonia.Controls/Primitives/ScrollBar.cs @@ -216,25 +216,25 @@ namespace Avalonia.Controls.Primitives private void SmallDecrement() { - Value = Math.Max(Value - SmallChange * ViewportSize, Minimum); + Value = Math.Max(Value - SmallChange, Minimum); OnScroll(ScrollEventType.SmallDecrement); } private void SmallIncrement() { - Value = Math.Min(Value + SmallChange * ViewportSize, Maximum); + Value = Math.Min(Value + SmallChange, Maximum); OnScroll(ScrollEventType.SmallIncrement); } private void LargeDecrement() { - Value = Math.Max(Value - LargeChange * ViewportSize, Minimum); + Value = Math.Max(Value - LargeChange, Minimum); OnScroll(ScrollEventType.LargeDecrement); } private void LargeIncrement() { - Value = Math.Min(Value + LargeChange * ViewportSize, Maximum); + Value = Math.Min(Value + LargeChange, Maximum); OnScroll(ScrollEventType.LargeIncrement); } diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs index 260c708515..bc4733296b 100644 --- a/src/Avalonia.Controls/Slider.cs +++ b/src/Avalonia.Controls/Slider.cs @@ -47,8 +47,6 @@ namespace Avalonia.Controls Thumb.DragStartedEvent.AddClassHandler(x => x.OnThumbDragStarted, RoutingStrategies.Bubble); Thumb.DragDeltaEvent.AddClassHandler(x => x.OnThumbDragDelta, RoutingStrategies.Bubble); Thumb.DragCompletedEvent.AddClassHandler(x => x.OnThumbDragCompleted, RoutingStrategies.Bubble); - SmallChangeProperty.OverrideDefaultValue(1); - LargeChangeProperty.OverrideDefaultValue(10); } /// diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs index 9921a8de6c..97d57e9eb6 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs @@ -15,6 +15,7 @@ using Avalonia.Input; using Avalonia.Layout; using Avalonia.Platform; using Avalonia.Rendering; +using Avalonia.Styling; using Avalonia.UnitTests; using Xunit; @@ -756,6 +757,80 @@ namespace Avalonia.Controls.UnitTests.Presenters Assert.Same(target.Panel.Children[9].DataContext, last); } + [Fact] + public void Scrolling_Less_Than_A_Page_Should_Move_Recycled_Items() + { + var target = CreateTarget(); + var items = (IList)target.Items; + + target.ApplyTemplate(); + target.Measure(new Size(100, 100)); + target.Arrange(new Rect(0, 0, 100, 100)); + + var containers = target.Panel.Children.ToList(); + var scroller = (ScrollContentPresenter)target.Parent; + + scroller.Offset = new Vector(0, 5); + + var scrolledContainers = containers + .Skip(5) + .Take(5) + .Concat(containers.Take(5)).ToList(); + + Assert.Equal(new Vector(0, 5), ((ILogicalScrollable)target).Offset); + Assert.Equal(scrolledContainers, target.Panel.Children); + + for (var i = 0; i < target.Panel.Children.Count; ++i) + { + Assert.Equal(items[i + 5], target.Panel.Children[i].DataContext); + } + + scroller.Offset = new Vector(0, 0); + Assert.Equal(new Vector(0, 0), ((ILogicalScrollable)target).Offset); + Assert.Equal(containers, target.Panel.Children); + + var dcs = target.Panel.Children.Select(x => x.DataContext).ToList(); + + for (var i = 0; i < target.Panel.Children.Count; ++i) + { + Assert.Equal(items[i], target.Panel.Children[i].DataContext); + } + } + + [Fact] + public void Scrolling_More_Than_A_Page_Should_Recycle_Items() + { + var target = CreateTarget(itemCount: 50); + var items = (IList)target.Items; + + target.ApplyTemplate(); + target.Measure(new Size(100, 100)); + target.Arrange(new Rect(0, 0, 100, 100)); + + var containers = target.Panel.Children.ToList(); + var scroller = (ScrollContentPresenter)target.Parent; + + scroller.Offset = new Vector(0, 20); + + Assert.Equal(new Vector(0, 20), ((ILogicalScrollable)target).Offset); + Assert.Equal(containers, target.Panel.Children); + + for (var i = 0; i < target.Panel.Children.Count; ++i) + { + Assert.Equal(items[i + 20], target.Panel.Children[i].DataContext); + } + + scroller.Offset = new Vector(0, 0); + + Assert.Equal(new Vector(0, 0), ((ILogicalScrollable)target).Offset); + Assert.Equal(containers, target.Panel.Children); + + for (var i = 0; i < target.Panel.Children.Count; ++i) + { + Assert.Equal(items[i], target.Panel.Children[i].DataContext); + } + } + public class Vertical { [Fact] @@ -941,86 +1016,8 @@ namespace Avalonia.Controls.UnitTests.Presenters } } - public class WithContainers - { - [Fact] - public void Scrolling_Less_Than_A_Page_Should_Move_Recycled_Items() - { - var target = CreateTarget(); - var items = (IList)target.Items; - - target.ApplyTemplate(); - target.Measure(new Size(100, 100)); - target.Arrange(new Rect(0, 0, 100, 100)); - - var containers = target.Panel.Children.ToList(); - var scroller = (ScrollContentPresenter)target.Parent; - - scroller.Offset = new Vector(0, 5); - - var scrolledContainers = containers - .Skip(5) - .Take(5) - .Concat(containers.Take(5)).ToList(); - - Assert.Equal(new Vector(0, 5), ((ILogicalScrollable)target).Offset); - Assert.Equal(scrolledContainers, target.Panel.Children); - - for (var i = 0; i < target.Panel.Children.Count; ++i) - { - Assert.Equal(items[i + 5], target.Panel.Children[i].DataContext); - } - - scroller.Offset = new Vector(0, 0); - Assert.Equal(new Vector(0, 0), ((ILogicalScrollable)target).Offset); - Assert.Equal(containers, target.Panel.Children); - - var dcs = target.Panel.Children.Select(x => x.DataContext).ToList(); - - for (var i = 0; i < target.Panel.Children.Count; ++i) - { - Assert.Equal(items[i], target.Panel.Children[i].DataContext); - } - } - - [Fact] - public void Scrolling_More_Than_A_Page_Should_Recycle_Items() - { - var target = CreateTarget(itemCount: 50); - var items = (IList)target.Items; - - target.ApplyTemplate(); - target.Measure(new Size(100, 100)); - target.Arrange(new Rect(0, 0, 100, 100)); - - var containers = target.Panel.Children.ToList(); - var scroller = (ScrollContentPresenter)target.Parent; - - scroller.Offset = new Vector(0, 20); - - Assert.Equal(new Vector(0, 20), ((ILogicalScrollable)target).Offset); - Assert.Equal(containers, target.Panel.Children); - - for (var i = 0; i < target.Panel.Children.Count; ++i) - { - Assert.Equal(items[i + 20], target.Panel.Children[i].DataContext); - } - - scroller.Offset = new Vector(0, 0); - - Assert.Equal(new Vector(0, 0), ((ILogicalScrollable)target).Offset); - Assert.Equal(containers, target.Panel.Children); - - for (var i = 0; i < target.Panel.Children.Count; ++i) - { - Assert.Equal(items[i], target.Panel.Children[i].DataContext); - } - } - } - private static ItemsPresenter CreateTarget( Orientation orientation = Orientation.Vertical, - bool useContainers = true, int itemCount = 20, bool useAvaloniaList = false) { @@ -1034,11 +1031,11 @@ namespace Avalonia.Controls.UnitTests.Presenters { CanHorizontallyScroll = true, CanVerticallyScroll = true, - Content = result = new TestItemsPresenter(useContainers) + Content = result = new TestItemsPresenter { Items = items, ItemsPanel = VirtualizingPanelTemplate(orientation), - ItemTemplate = ItemTemplate(), + DataTemplates = { StringDataTemplate() }, VirtualizationMode = ItemVirtualizationMode.Simple, } }; @@ -1047,7 +1044,7 @@ namespace Avalonia.Controls.UnitTests.Presenters return result; } - private static IDataTemplate ItemTemplate() + private static IDataTemplate StringDataTemplate() { return new FuncDataTemplate(x => new Canvas { @@ -1065,7 +1062,7 @@ namespace Avalonia.Controls.UnitTests.Presenters }); } - private class TestScroller : ScrollContentPresenter, IRenderRoot, ILayoutRoot + private class TestScroller : ScrollContentPresenter, IRenderRoot, ILayoutRoot, IStyleRoot { public IRenderer Renderer { get; } public Size ClientSize { get; } @@ -1085,18 +1082,12 @@ namespace Avalonia.Controls.UnitTests.Presenters private class TestItemsPresenter : ItemsPresenter { - private bool _useContainers; - - public TestItemsPresenter(bool useContainers) - { - _useContainers = useContainers; - } - protected override IItemContainerGenerator CreateItemContainerGenerator() { - return _useContainers ? - new ItemContainerGenerator(this, TestContainer.ContentProperty, null) : - new ItemContainerGenerator(this); + return new ItemContainerGenerator( + this, + TestContainer.ContentProperty, + null); } } @@ -1104,8 +1095,12 @@ namespace Avalonia.Controls.UnitTests.Presenters { public TestContainer() { - Width = 10; - Height = 10; + Template = new FuncControlTemplate(parent => new ContentPresenter + { + Name = "PART_ContentPresenter", + [~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty], + [~ContentPresenter.ContentTemplateProperty] = parent[~ContentControl.ContentTemplateProperty], + }); } } }