From 15f3b746a83f446c724741caa5524086b73bea7c Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Tue, 7 Feb 2023 17:10:54 +0100 Subject: [PATCH] Nullability fixes for Avalonia.Controls.ItemsRepeater --- .../Controls/ItemsRepeater.cs | 93 ++++++------------- .../Controls/ViewManager.cs | 15 +-- .../Controls/ViewportManager.cs | 14 +-- 3 files changed, 42 insertions(+), 80 deletions(-) diff --git a/src/Avalonia.Controls.ItemsRepeater/Controls/ItemsRepeater.cs b/src/Avalonia.Controls.ItemsRepeater/Controls/ItemsRepeater.cs index 6c761ab4cf..951e60c25b 100644 --- a/src/Avalonia.Controls.ItemsRepeater/Controls/ItemsRepeater.cs +++ b/src/Avalonia.Controls.ItemsRepeater/Controls/ItemsRepeater.cs @@ -44,8 +44,8 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly StyledProperty LayoutProperty = - AvaloniaProperty.Register(nameof(Layout), new StackLayout()); + public static readonly StyledProperty LayoutProperty = + AvaloniaProperty.Register(nameof(Layout), new StackLayout()); /// /// Defines the property. @@ -53,8 +53,8 @@ namespace Avalonia.Controls public static readonly StyledProperty VerticalCacheLengthProperty = AvaloniaProperty.Register(nameof(VerticalCacheLength), 2.0); - private static readonly StyledProperty VirtualizationInfoProperty = - AvaloniaProperty.RegisterAttached("VirtualizationInfo"); + private static readonly StyledProperty VirtualizationInfoProperty = + AvaloniaProperty.RegisterAttached("VirtualizationInfo"); internal static readonly Rect InvalidRect = new Rect(-1, -1, -1, -1); internal static readonly Point ClearedElementsArrangePosition = new Point(-10000.0, -10000.0); @@ -63,7 +63,7 @@ namespace Avalonia.Controls private readonly ViewportManager _viewportManager; private readonly TargetWeakEventSubscriber _layoutWeakSubscriber; private IEnumerable? _items; - private VirtualizingLayoutContext? _layoutContext; + private RepeaterLayoutContext? _layoutContext; private EventHandler? _childIndexChanged; private bool _isLayoutInProgress; private NotifyCollectionChangedEventArgs? _processingItemsSourceChange; @@ -104,7 +104,7 @@ namespace Avalonia.Controls /// The layout used to size and position elements. The default is a StackLayout with /// vertical orientation. /// - public AttachedLayout Layout + public AttachedLayout? Layout { get => GetValue(LayoutProperty); set => SetValue(LayoutProperty, value); @@ -164,18 +164,7 @@ namespace Avalonia.Controls private bool IsProcessingCollectionChange => _processingItemsSourceChange != null; - private LayoutContext LayoutContext - { - get - { - if (_layoutContext == null) - { - _layoutContext = new RepeaterLayoutContext(this); - } - - return _layoutContext; - } - } + private RepeaterLayoutContext LayoutContext => _layoutContext ??= new RepeaterLayoutContext(this); event EventHandler? IChildIndexProvider.ChildIndexChanged { @@ -269,39 +258,22 @@ namespace Avalonia.Controls internal void UnpinElement(Control element) => _viewManager.UpdatePin(element, false); - internal static VirtualizationInfo? TryGetVirtualizationInfo(Control element) + internal static VirtualizationInfo? TryGetVirtualizationInfo(Control? element) { - return (element as AvaloniaObject)?.GetValue(VirtualizationInfoProperty); - } - - internal static VirtualizationInfo CreateAndInitializeVirtualizationInfo(Control element) - { - if (TryGetVirtualizationInfo(element) != null) - { - throw new InvalidOperationException("VirtualizationInfo already created."); - } - - var result = new VirtualizationInfo(); - element.SetValue(VirtualizationInfoProperty, result); - return result; + return element?.GetValue(VirtualizationInfoProperty); } internal static VirtualizationInfo GetVirtualizationInfo(Control element) { - if (element is AvaloniaObject ao) - { - var result = ao.GetValue(VirtualizationInfoProperty); - - if (result == null) - { - result = new VirtualizationInfo(); - ao.SetValue(VirtualizationInfoProperty, result); - } + var result = element.GetValue(VirtualizationInfoProperty); - return result; + if (result == null) + { + result = new VirtualizationInfo(); + element.SetValue(VirtualizationInfoProperty, result); } - throw new NotSupportedException("Custom implementations of AvaloniaObject not supported."); + return result; } private protected override void InvalidateMeasureOnChildrenChanged() @@ -309,6 +281,7 @@ namespace Avalonia.Controls // Don't invalidate measure when children change. } + /// protected override Size MeasureOverride(Size availableSize) { if (_isLayoutInProgress) @@ -334,7 +307,7 @@ namespace Avalonia.Controls if (layout != null) { - var layoutContext = GetLayoutContext(); + var layoutContext = LayoutContext; desiredSize = layout.Measure(layoutContext, availableSize); extent = new Rect(LayoutOrigin.X, LayoutOrigin.Y, desiredSize.Width, desiredSize.Height); @@ -364,6 +337,7 @@ namespace Avalonia.Controls } } + /// protected override Size ArrangeOverride(Size finalSize) { if (_isLayoutInProgress) @@ -380,7 +354,7 @@ namespace Avalonia.Controls try { - var arrangeSize = Layout?.Arrange(GetLayoutContext(), finalSize) ?? default; + var arrangeSize = Layout?.Arrange(LayoutContext, finalSize) ?? default; // The view manager might clear elements during this call. // That's why we call it before arranging cleared elements @@ -421,6 +395,7 @@ namespace Avalonia.Controls } } + /// protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); @@ -428,11 +403,13 @@ namespace Avalonia.Controls _viewportManager.ResetScrollers(); } + /// protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { _viewportManager.ResetScrollers(); } + /// protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { if (change.Property == ItemsProperty) @@ -501,7 +478,7 @@ namespace Avalonia.Controls if (parent == this) { var virtInfo = TryGetVirtualizationInfo(element); - return _viewManager.GetElementIndex(virtInfo!); + return _viewManager.GetElementIndex(virtInfo); } return -1; @@ -529,7 +506,7 @@ namespace Avalonia.Controls { if (index >= 0 && index >= (ItemsSourceView?.Count ?? 0)) { - throw new ArgumentException("Argument index is invalid.", "index"); + throw new ArgumentException("Argument index is invalid.", nameof(index)); } if (_isLayoutInProgress) @@ -547,7 +524,7 @@ namespace Avalonia.Controls throw new InvalidOperationException("Cannot make an Anchor when there is no attached layout."); } - element = (Control)GetLayoutContext().GetOrCreateElementAt(index); + element = (Control)LayoutContext.GetOrCreateElementAt(index); element.Measure(Size.Infinity); } @@ -647,9 +624,9 @@ namespace Avalonia.Controls if (Layout is VirtualizingLayout virtualLayout) { - virtualLayout.OnItemsChanged(GetLayoutContext(), newValue, args); + virtualLayout.OnItemsChanged(LayoutContext, newValue, args); } - else if (Layout is NonVirtualizingLayout nonVirtualLayout) + else if (Layout is NonVirtualizingLayout) { // Walk through all the elements and make sure they are cleared for // non-virtualizing layouts. @@ -693,7 +670,7 @@ namespace Avalonia.Controls try { - virtualLayout.OnItemsChanged(GetLayoutContext(), newValue, args); + virtualLayout.OnItemsChanged(LayoutContext, newValue, args); } finally { @@ -760,7 +737,7 @@ namespace Avalonia.Controls AttachedLayout.ArrangeInvalidatedWeakEvent.Subscribe(newValue, _layoutWeakSubscriber); } - bool isVirtualizingLayout = newValue != null && newValue is VirtualizingLayout; + bool isVirtualizingLayout = newValue is VirtualizingLayout; _viewportManager.OnLayoutChanged(isVirtualizingLayout); InvalidateMeasure(); } @@ -788,7 +765,7 @@ namespace Avalonia.Controls { if (Layout is VirtualizingLayout virtualLayout) { - virtualLayout.OnItemsChanged(GetLayoutContext(), sender, args); + virtualLayout.OnItemsChanged(LayoutContext, sender, args); } else { @@ -807,15 +784,5 @@ namespace Avalonia.Controls { _viewportManager.OnBringIntoViewRequested(e); } - - private VirtualizingLayoutContext GetLayoutContext() - { - if (_layoutContext == null) - { - _layoutContext = new RepeaterLayoutContext(this); - } - - return _layoutContext; - } } } diff --git a/src/Avalonia.Controls.ItemsRepeater/Controls/ViewManager.cs b/src/Avalonia.Controls.ItemsRepeater/Controls/ViewManager.cs index 2d302a95dd..6b9d7934bf 100644 --- a/src/Avalonia.Controls.ItemsRepeater/Controls/ViewManager.cs +++ b/src/Avalonia.Controls.ItemsRepeater/Controls/ViewManager.cs @@ -53,7 +53,7 @@ namespace Avalonia.Controls } } } - if (element == null) { element = GetElementFromUniqueIdResetPool(index); }; + if (element == null) { element = GetElementFromUniqueIdResetPool(index); } if (element == null) { element = GetElementFromPinnedElements(index); } if (element == null) { element = GetElementFromElementFactory(index); } @@ -221,7 +221,7 @@ namespace Avalonia.Controls return nextElement; } - public int GetElementIndex(VirtualizationInfo virtInfo) + public int GetElementIndex(VirtualizationInfo? virtInfo) { if (virtInfo == null) { @@ -627,11 +627,7 @@ namespace Avalonia.Controls var element = GetElement(); - var virtInfo = ItemsRepeater.TryGetVirtualizationInfo(element); - if (virtInfo == null) - { - virtInfo = ItemsRepeater.CreateAndInitializeVirtualizationInfo(element); - } + var virtInfo = ItemsRepeater.GetVirtualizationInfo(element); // Clear flag virtInfo.MustClearDataContext = false; @@ -710,9 +706,8 @@ namespace Avalonia.Controls { if (parent is ItemsRepeater repeater) { - var element = child as Control; if (repeater == owner && - element is not null && + child is Control element && ItemsRepeater.GetVirtualizationInfo(element).IsRealized) { focusedElement = element; @@ -722,7 +717,7 @@ namespace Avalonia.Controls } child = parent; - parent = child?.GetVisualParent(); + parent = child.GetVisualParent(); } } diff --git a/src/Avalonia.Controls.ItemsRepeater/Controls/ViewportManager.cs b/src/Avalonia.Controls.ItemsRepeater/Controls/ViewportManager.cs index 336fb2d228..6ed817c238 100644 --- a/src/Avalonia.Controls.ItemsRepeater/Controls/ViewportManager.cs +++ b/src/Avalonia.Controls.ItemsRepeater/Controls/ViewportManager.cs @@ -166,7 +166,7 @@ namespace Avalonia.Controls if (Math.Abs(_expectedViewportShift.X) > 1 || Math.Abs(_expectedViewportShift.Y) > 1) { Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Expecting viewport shift of ({Shift})", - _owner.Layout.LayoutId, _expectedViewportShift); + _owner.Layout?.LayoutId, _expectedViewportShift); // There are cases where we might be expecting a shift but not get it. We will // be waiting for the effective viewport event but if the scroll viewer is not able @@ -287,7 +287,7 @@ namespace Avalonia.Controls if (_pendingViewportShift.X != 0 || _pendingViewportShift.Y != 0) { Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Layout Updated with pending shift {Shift}- invalidating measure", - _owner.Layout.LayoutId, + _owner.Layout?.LayoutId, _pendingViewportShift); // Assume this is never going to come. @@ -436,7 +436,7 @@ namespace Avalonia.Controls private void OnEffectiveViewportChanged(object? sender, EffectiveViewportChangedEventArgs e) { - Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: EffectiveViewportChanged event callback", _owner.Layout.LayoutId); + Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: EffectiveViewportChanged event callback", _owner.Layout?.LayoutId); UpdateViewport(e.EffectiveViewport); _pendingViewportShift = default; @@ -490,14 +490,14 @@ namespace Avalonia.Controls var previousVisibleWindow = _visibleWindow; Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Effective Viewport: ({Before})->({After})", - _owner.Layout.LayoutId, + _owner.Layout?.LayoutId, previousVisibleWindow, viewport); if (-currentVisibleWindow.X <= ItemsRepeater.ClearedElementsArrangePosition.X && -currentVisibleWindow.Y <= ItemsRepeater.ClearedElementsArrangePosition.Y) { - Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Viewport is invalid. visible window cleared", _owner.Layout.LayoutId); + Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Viewport is invalid. visible window cleared", _owner.Layout?.LayoutId); // We got cleared. _visibleWindow = default; } @@ -509,7 +509,7 @@ namespace Avalonia.Controls if (_visibleWindow != previousVisibleWindow) { Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Used Viewport: ({Before})->({After})", - _owner.Layout.LayoutId, + _owner.Layout?.LayoutId, previousVisibleWindow, currentVisibleWindow); TryInvalidateMeasure(); @@ -532,7 +532,7 @@ namespace Avalonia.Controls // We invalidate measure instead of just invalidating arrange because // we don't invalidate measure in UpdateViewport if the view is changing to // avoid layout cycles. - Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Invalidating measure due to viewport change", _owner.Layout.LayoutId); + Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Invalidating measure due to viewport change", _owner.Layout?.LayoutId); _owner.InvalidateMeasure(); } }