Browse Source

Nullability fixes for Avalonia.Controls.ItemsRepeater

pull/10256/head
Julien Lebosquain 4 years ago
parent
commit
15f3b746a8
No known key found for this signature in database GPG Key ID: 1833CAD10ACC46FD
  1. 93
      src/Avalonia.Controls.ItemsRepeater/Controls/ItemsRepeater.cs
  2. 15
      src/Avalonia.Controls.ItemsRepeater/Controls/ViewManager.cs
  3. 14
      src/Avalonia.Controls.ItemsRepeater/Controls/ViewportManager.cs

93
src/Avalonia.Controls.ItemsRepeater/Controls/ItemsRepeater.cs

@ -44,8 +44,8 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="Layout"/> property.
/// </summary>
public static readonly StyledProperty<AttachedLayout> LayoutProperty =
AvaloniaProperty.Register<ItemsRepeater, AttachedLayout>(nameof(Layout), new StackLayout());
public static readonly StyledProperty<AttachedLayout?> LayoutProperty =
AvaloniaProperty.Register<ItemsRepeater, AttachedLayout?>(nameof(Layout), new StackLayout());
/// <summary>
/// Defines the <see cref="VerticalCacheLength"/> property.
@ -53,8 +53,8 @@ namespace Avalonia.Controls
public static readonly StyledProperty<double> VerticalCacheLengthProperty =
AvaloniaProperty.Register<ItemsRepeater, double>(nameof(VerticalCacheLength), 2.0);
private static readonly StyledProperty<VirtualizationInfo> VirtualizationInfoProperty =
AvaloniaProperty.RegisterAttached<ItemsRepeater, Control, VirtualizationInfo>("VirtualizationInfo");
private static readonly StyledProperty<VirtualizationInfo?> VirtualizationInfoProperty =
AvaloniaProperty.RegisterAttached<ItemsRepeater, Control, VirtualizationInfo?>("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<ItemsRepeater, EventArgs> _layoutWeakSubscriber;
private IEnumerable? _items;
private VirtualizingLayoutContext? _layoutContext;
private RepeaterLayoutContext? _layoutContext;
private EventHandler<ChildIndexChangedEventArgs>? _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.
/// </value>
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<ChildIndexChangedEventArgs>? 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.
}
/// <inheritdoc />
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
}
}
/// <inheritdoc />
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
}
}
/// <inheritdoc />
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
@ -428,11 +403,13 @@ namespace Avalonia.Controls
_viewportManager.ResetScrollers();
}
/// <inheritdoc />
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
_viewportManager.ResetScrollers();
}
/// <inheritdoc />
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;
}
}
}

15
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();
}
}

14
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();
}
}

Loading…
Cancel
Save