From 094462639e10fe743786438bffa847abaaa64a35 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 14 May 2020 07:59:38 +0200 Subject: [PATCH 1/3] Add StackLayout.DisableVirtualization And a couple of updates to `ItemsRepeater`. Ported from https://github.com/microsoft/microsoft-ui-xaml/commit/f3862ecf86530a9341002fcab287ae1dcfccaf53 --- .../Repeater/ItemsRepeater.cs | 28 ++++++++++++++----- src/Avalonia.Layout/FlowLayoutAlgorithm.cs | 10 ++++--- src/Avalonia.Layout/StackLayout.cs | 16 +++++++++++ src/Avalonia.Layout/UniformGridLayout.cs | 1 + 4 files changed, 44 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs index 086599d0bb..12fba12c8c 100644 --- a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs +++ b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs @@ -10,6 +10,7 @@ using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Input; using Avalonia.Layout; +using Avalonia.VisualTree; namespace Avalonia.Controls { @@ -379,14 +380,19 @@ namespace Avalonia.Controls { if (property == ItemsProperty) { + var oldEnumerable = oldValue.GetValueOrDefault(); var newEnumerable = newValue.GetValueOrDefault(); - var newDataSource = newEnumerable as ItemsSourceView; - if (newEnumerable != null && newDataSource == null) + + if (oldEnumerable != newEnumerable) { - newDataSource = new ItemsSourceView(newEnumerable); - } + var newDataSource = newEnumerable as ItemsSourceView; + if (newEnumerable != null && newDataSource == null) + { + newDataSource = new ItemsSourceView(newEnumerable); + } - OnDataSourcePropertyChanged(ItemsSourceView, newDataSource); + OnDataSourcePropertyChanged(ItemsSourceView, newDataSource); + } } else if (property == ItemTemplateProperty) { @@ -431,8 +437,16 @@ namespace Avalonia.Controls private int GetElementIndexImpl(IControl element) { - var virtInfo = TryGetVirtualizationInfo(element); - return _viewManager.GetElementIndex(virtInfo); + // Verify that element is actually a child of this ItemsRepeater + var parent = element.GetVisualParent(); + + if (parent == this) + { + var virtInfo = TryGetVirtualizationInfo(element); + return _viewManager.GetElementIndex(virtInfo); + } + + return -1; } private IControl GetElementFromIndexImpl(int index) diff --git a/src/Avalonia.Layout/FlowLayoutAlgorithm.cs b/src/Avalonia.Layout/FlowLayoutAlgorithm.cs index 7f44c80a64..0d64f8dfd5 100644 --- a/src/Avalonia.Layout/FlowLayoutAlgorithm.cs +++ b/src/Avalonia.Layout/FlowLayoutAlgorithm.cs @@ -74,6 +74,7 @@ namespace Avalonia.Layout double lineSpacing, int maxItemsPerLine, ScrollOrientation orientation, + bool disableVirtualization, string layoutId) { _orientation.ScrollOrientation = orientation; @@ -95,14 +96,14 @@ namespace Avalonia.Layout _elementManager.OnBeginMeasure(orientation); int anchorIndex = GetAnchorIndex(availableSize, isWrapping, minItemSpacing, layoutId); - Generate(GenerateDirection.Forward, anchorIndex, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, layoutId); - Generate(GenerateDirection.Backward, anchorIndex, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, layoutId); + Generate(GenerateDirection.Forward, anchorIndex, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, disableVirtualization, layoutId); + Generate(GenerateDirection.Backward, anchorIndex, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, disableVirtualization, layoutId); if (isWrapping && IsReflowRequired()) { var firstElementBounds = _elementManager.GetLayoutBoundsForRealizedIndex(0); _orientation.SetMinorStart(ref firstElementBounds, 0); _elementManager.SetLayoutBoundsForRealizedIndex(0, firstElementBounds); - Generate(GenerateDirection.Forward, 0 /*anchorIndex*/, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, layoutId); + Generate(GenerateDirection.Forward, 0 /*anchorIndex*/, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, disableVirtualization, layoutId); } RaiseLineArranged(); @@ -273,6 +274,7 @@ namespace Avalonia.Layout double minItemSpacing, double lineSpacing, int maxItemsPerLine, + bool disableVirtualization, string layoutId) { if (anchorIndex != -1) @@ -288,7 +290,7 @@ namespace Avalonia.Layout bool lineNeedsReposition = false; while (_elementManager.IsIndexValidInData(currentIndex) && - ShouldContinueFillingUpSpace(previousIndex, direction)) + (disableVirtualization || ShouldContinueFillingUpSpace(previousIndex, direction))) { // Ensure layout element. _elementManager.EnsureElementRealized(direction == GenerateDirection.Forward, currentIndex, layoutId); diff --git a/src/Avalonia.Layout/StackLayout.cs b/src/Avalonia.Layout/StackLayout.cs index 9b8eb4814e..b383c205f9 100644 --- a/src/Avalonia.Layout/StackLayout.cs +++ b/src/Avalonia.Layout/StackLayout.cs @@ -14,6 +14,12 @@ namespace Avalonia.Layout /// public class StackLayout : VirtualizingLayout, IFlowLayoutAlgorithmDelegates { + /// + /// Defines the property. + /// + public static readonly StyledProperty DisableVirtualizationProperty = + AvaloniaProperty.Register(nameof(DisableVirtualization)); + /// /// Defines the property. /// @@ -36,6 +42,15 @@ namespace Avalonia.Layout LayoutId = "StackLayout"; } + /// + /// Gets or sets a value indicating whether virtualization is disabled on the layout. + /// + public bool DisableVirtualization + { + get => GetValue(DisableVirtualizationProperty); + set => SetValue(DisableVirtualizationProperty, value); + } + /// /// Gets or sets the axis along which items are laid out. /// @@ -270,6 +285,7 @@ namespace Avalonia.Layout Spacing, int.MaxValue, _orientation.ScrollOrientation, + DisableVirtualization, LayoutId); return new Size(desiredSize.Width, desiredSize.Height); diff --git a/src/Avalonia.Layout/UniformGridLayout.cs b/src/Avalonia.Layout/UniformGridLayout.cs index ee9cff4a01..d565fae074 100644 --- a/src/Avalonia.Layout/UniformGridLayout.cs +++ b/src/Avalonia.Layout/UniformGridLayout.cs @@ -433,6 +433,7 @@ namespace Avalonia.Layout LineSpacing, _maximumRowsOrColumns, _orientation.ScrollOrientation, + false, LayoutId); // If after Measure the first item is in the realization rect, then we revoke grid state's ownership, From 6eb7f5b034aabb41edb2419cee09b524f5d0c0d2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 14 May 2020 08:09:26 +0200 Subject: [PATCH 2/3] Fix issue with multiple resets causing a crash due to duplicate keys. Ported from https://github.com/microsoft/microsoft-ui-xaml/commit/464a4482772bd54f2b72cbaa8012704649120b32 --- src/Avalonia.Controls/Repeater/ViewManager.cs | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Controls/Repeater/ViewManager.cs b/src/Avalonia.Controls/Repeater/ViewManager.cs index 7d005a30b4..4ed9cb3333 100644 --- a/src/Avalonia.Controls/Repeater/ViewManager.cs +++ b/src/Avalonia.Controls/Repeater/ViewManager.cs @@ -388,19 +388,24 @@ namespace Avalonia.Controls } case NotifyCollectionChangedAction.Reset: - if (_owner.ItemsSourceView.HasKeyIndexMapping) + // If we get multiple resets back to back before + // running layout, we dont have to clear all the elements again. + if (!_isDataSourceStableResetPending) { - _isDataSourceStableResetPending = true; - } + if (_owner.ItemsSourceView.HasKeyIndexMapping) + { + _isDataSourceStableResetPending = true; + } - // Walk through all the elements and make sure they are cleared, they will go into - // the stable id reset pool. - foreach (var element in _owner.Children) - { - var virtInfo = ItemsRepeater.GetVirtualizationInfo(element); - if (virtInfo.IsRealized && virtInfo.AutoRecycleCandidate) + // Walk through all the elements and make sure they are cleared, they will go into + // the stable id reset pool. + foreach (var element in _owner.Children) { - _owner.ClearElementImpl(element); + var virtInfo = ItemsRepeater.GetVirtualizationInfo(element); + if (virtInfo.IsRealized && virtInfo.AutoRecycleCandidate) + { + _owner.ClearElementImpl(element); + } } } @@ -441,6 +446,9 @@ namespace Avalonia.Controls } _resetPool.Clear(); + + // Flush the realized indices once the stable reset pool is cleared to start fresh. + InvalidateRealizedIndicesHeldByLayout(); } } @@ -498,6 +506,10 @@ namespace Avalonia.Controls var virtInfo = ItemsRepeater.GetVirtualizationInfo(element); virtInfo.MoveOwnershipToLayoutFromUniqueIdResetPool(); UpdateElementIndex(element, virtInfo, index); + + // Update realized indices + _firstRealizedElementIndexHeldByLayout = Math.Min(_firstRealizedElementIndexHeldByLayout, index); + _lastRealizedElementIndexHeldByLayout = Math.Max(_lastRealizedElementIndexHeldByLayout, index); } } @@ -519,6 +531,10 @@ namespace Avalonia.Controls _pinnedPool.RemoveAt(i); element = elementInfo.PinnedElement; elementInfo.VirtualizationInfo.MoveOwnershipToLayoutFromPinnedPool(); + + // Update realized indices + _firstRealizedElementIndexHeldByLayout = Math.Min(_firstRealizedElementIndexHeldByLayout, index); + _lastRealizedElementIndexHeldByLayout = Math.Max(_lastRealizedElementIndexHeldByLayout, index); break; } } From 310d594461463a0010f9cee4c65391cee2577be5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 14 May 2020 08:34:33 +0200 Subject: [PATCH 3/3] StackLayout incorrect sizing bug Ported from https://github.com/microsoft/microsoft-ui-xaml/commit/c621498e61909850f1d7d0e760a296355f75d58f --- src/Avalonia.Layout/StackLayout.cs | 4 ++-- src/Avalonia.Layout/StackLayoutState.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Layout/StackLayout.cs b/src/Avalonia.Layout/StackLayout.cs index b383c205f9..d11875b2c1 100644 --- a/src/Avalonia.Layout/StackLayout.cs +++ b/src/Avalonia.Layout/StackLayout.cs @@ -277,6 +277,8 @@ namespace Avalonia.Layout protected internal override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize) { + ((StackLayoutState)context.LayoutState).OnMeasureStart(); + var desiredSize = GetFlowAlgorithm(context).Measure( availableSize, context, @@ -300,8 +302,6 @@ namespace Avalonia.Layout FlowLayoutAlgorithm.LineAlignment.Start, LayoutId); - ((StackLayoutState)context.LayoutState).OnArrangeLayoutEnd(); - return new Size(value.Width, value.Height); } diff --git a/src/Avalonia.Layout/StackLayoutState.cs b/src/Avalonia.Layout/StackLayoutState.cs index 05ad9bca8e..e6164e02e6 100644 --- a/src/Avalonia.Layout/StackLayoutState.cs +++ b/src/Avalonia.Layout/StackLayoutState.cs @@ -56,6 +56,6 @@ namespace Avalonia.Layout MaxArrangeBounds = Math.Max(MaxArrangeBounds, minorSize); } - internal void OnArrangeLayoutEnd() => MaxArrangeBounds = 0; + internal void OnMeasureStart() => MaxArrangeBounds = 0; } }