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> /// <summary>
/// Defines the <see cref="Layout"/> property. /// Defines the <see cref="Layout"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<AttachedLayout> LayoutProperty = public static readonly StyledProperty<AttachedLayout?> LayoutProperty =
AvaloniaProperty.Register<ItemsRepeater, AttachedLayout>(nameof(Layout), new StackLayout()); AvaloniaProperty.Register<ItemsRepeater, AttachedLayout?>(nameof(Layout), new StackLayout());
/// <summary> /// <summary>
/// Defines the <see cref="VerticalCacheLength"/> property. /// Defines the <see cref="VerticalCacheLength"/> property.
@ -53,8 +53,8 @@ namespace Avalonia.Controls
public static readonly StyledProperty<double> VerticalCacheLengthProperty = public static readonly StyledProperty<double> VerticalCacheLengthProperty =
AvaloniaProperty.Register<ItemsRepeater, double>(nameof(VerticalCacheLength), 2.0); AvaloniaProperty.Register<ItemsRepeater, double>(nameof(VerticalCacheLength), 2.0);
private static readonly StyledProperty<VirtualizationInfo> VirtualizationInfoProperty = private static readonly StyledProperty<VirtualizationInfo?> VirtualizationInfoProperty =
AvaloniaProperty.RegisterAttached<ItemsRepeater, Control, VirtualizationInfo>("VirtualizationInfo"); AvaloniaProperty.RegisterAttached<ItemsRepeater, Control, VirtualizationInfo?>("VirtualizationInfo");
internal static readonly Rect InvalidRect = new Rect(-1, -1, -1, -1); internal static readonly Rect InvalidRect = new Rect(-1, -1, -1, -1);
internal static readonly Point ClearedElementsArrangePosition = new Point(-10000.0, -10000.0); internal static readonly Point ClearedElementsArrangePosition = new Point(-10000.0, -10000.0);
@ -63,7 +63,7 @@ namespace Avalonia.Controls
private readonly ViewportManager _viewportManager; private readonly ViewportManager _viewportManager;
private readonly TargetWeakEventSubscriber<ItemsRepeater, EventArgs> _layoutWeakSubscriber; private readonly TargetWeakEventSubscriber<ItemsRepeater, EventArgs> _layoutWeakSubscriber;
private IEnumerable? _items; private IEnumerable? _items;
private VirtualizingLayoutContext? _layoutContext; private RepeaterLayoutContext? _layoutContext;
private EventHandler<ChildIndexChangedEventArgs>? _childIndexChanged; private EventHandler<ChildIndexChangedEventArgs>? _childIndexChanged;
private bool _isLayoutInProgress; private bool _isLayoutInProgress;
private NotifyCollectionChangedEventArgs? _processingItemsSourceChange; private NotifyCollectionChangedEventArgs? _processingItemsSourceChange;
@ -104,7 +104,7 @@ namespace Avalonia.Controls
/// The layout used to size and position elements. The default is a StackLayout with /// The layout used to size and position elements. The default is a StackLayout with
/// vertical orientation. /// vertical orientation.
/// </value> /// </value>
public AttachedLayout Layout public AttachedLayout? Layout
{ {
get => GetValue(LayoutProperty); get => GetValue(LayoutProperty);
set => SetValue(LayoutProperty, value); set => SetValue(LayoutProperty, value);
@ -164,18 +164,7 @@ namespace Avalonia.Controls
private bool IsProcessingCollectionChange => _processingItemsSourceChange != null; private bool IsProcessingCollectionChange => _processingItemsSourceChange != null;
private LayoutContext LayoutContext private RepeaterLayoutContext LayoutContext => _layoutContext ??= new RepeaterLayoutContext(this);
{
get
{
if (_layoutContext == null)
{
_layoutContext = new RepeaterLayoutContext(this);
}
return _layoutContext;
}
}
event EventHandler<ChildIndexChangedEventArgs>? IChildIndexProvider.ChildIndexChanged event EventHandler<ChildIndexChangedEventArgs>? IChildIndexProvider.ChildIndexChanged
{ {
@ -269,39 +258,22 @@ namespace Avalonia.Controls
internal void UnpinElement(Control element) => _viewManager.UpdatePin(element, false); 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); return element?.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;
} }
internal static VirtualizationInfo GetVirtualizationInfo(Control element) internal static VirtualizationInfo GetVirtualizationInfo(Control element)
{ {
if (element is AvaloniaObject ao) var result = element.GetValue(VirtualizationInfoProperty);
{
var result = ao.GetValue(VirtualizationInfoProperty);
if (result == null)
{
result = new VirtualizationInfo();
ao.SetValue(VirtualizationInfoProperty, result);
}
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() private protected override void InvalidateMeasureOnChildrenChanged()
@ -309,6 +281,7 @@ namespace Avalonia.Controls
// Don't invalidate measure when children change. // Don't invalidate measure when children change.
} }
/// <inheritdoc />
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
if (_isLayoutInProgress) if (_isLayoutInProgress)
@ -334,7 +307,7 @@ namespace Avalonia.Controls
if (layout != null) if (layout != null)
{ {
var layoutContext = GetLayoutContext(); var layoutContext = LayoutContext;
desiredSize = layout.Measure(layoutContext, availableSize); desiredSize = layout.Measure(layoutContext, availableSize);
extent = new Rect(LayoutOrigin.X, LayoutOrigin.Y, desiredSize.Width, desiredSize.Height); 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) protected override Size ArrangeOverride(Size finalSize)
{ {
if (_isLayoutInProgress) if (_isLayoutInProgress)
@ -380,7 +354,7 @@ namespace Avalonia.Controls
try try
{ {
var arrangeSize = Layout?.Arrange(GetLayoutContext(), finalSize) ?? default; var arrangeSize = Layout?.Arrange(LayoutContext, finalSize) ?? default;
// The view manager might clear elements during this call. // The view manager might clear elements during this call.
// That's why we call it before arranging cleared elements // That's why we call it before arranging cleared elements
@ -421,6 +395,7 @@ namespace Avalonia.Controls
} }
} }
/// <inheritdoc />
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnAttachedToVisualTree(e); base.OnAttachedToVisualTree(e);
@ -428,11 +403,13 @@ namespace Avalonia.Controls
_viewportManager.ResetScrollers(); _viewportManager.ResetScrollers();
} }
/// <inheritdoc />
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{ {
_viewportManager.ResetScrollers(); _viewportManager.ResetScrollers();
} }
/// <inheritdoc />
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{ {
if (change.Property == ItemsProperty) if (change.Property == ItemsProperty)
@ -501,7 +478,7 @@ namespace Avalonia.Controls
if (parent == this) if (parent == this)
{ {
var virtInfo = TryGetVirtualizationInfo(element); var virtInfo = TryGetVirtualizationInfo(element);
return _viewManager.GetElementIndex(virtInfo!); return _viewManager.GetElementIndex(virtInfo);
} }
return -1; return -1;
@ -529,7 +506,7 @@ namespace Avalonia.Controls
{ {
if (index >= 0 && index >= (ItemsSourceView?.Count ?? 0)) 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) if (_isLayoutInProgress)
@ -547,7 +524,7 @@ namespace Avalonia.Controls
throw new InvalidOperationException("Cannot make an Anchor when there is no attached layout."); 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); element.Measure(Size.Infinity);
} }
@ -647,9 +624,9 @@ namespace Avalonia.Controls
if (Layout is VirtualizingLayout virtualLayout) 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 // Walk through all the elements and make sure they are cleared for
// non-virtualizing layouts. // non-virtualizing layouts.
@ -693,7 +670,7 @@ namespace Avalonia.Controls
try try
{ {
virtualLayout.OnItemsChanged(GetLayoutContext(), newValue, args); virtualLayout.OnItemsChanged(LayoutContext, newValue, args);
} }
finally finally
{ {
@ -760,7 +737,7 @@ namespace Avalonia.Controls
AttachedLayout.ArrangeInvalidatedWeakEvent.Subscribe(newValue, _layoutWeakSubscriber); AttachedLayout.ArrangeInvalidatedWeakEvent.Subscribe(newValue, _layoutWeakSubscriber);
} }
bool isVirtualizingLayout = newValue != null && newValue is VirtualizingLayout; bool isVirtualizingLayout = newValue is VirtualizingLayout;
_viewportManager.OnLayoutChanged(isVirtualizingLayout); _viewportManager.OnLayoutChanged(isVirtualizingLayout);
InvalidateMeasure(); InvalidateMeasure();
} }
@ -788,7 +765,7 @@ namespace Avalonia.Controls
{ {
if (Layout is VirtualizingLayout virtualLayout) if (Layout is VirtualizingLayout virtualLayout)
{ {
virtualLayout.OnItemsChanged(GetLayoutContext(), sender, args); virtualLayout.OnItemsChanged(LayoutContext, sender, args);
} }
else else
{ {
@ -807,15 +784,5 @@ namespace Avalonia.Controls
{ {
_viewportManager.OnBringIntoViewRequested(e); _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 = GetElementFromPinnedElements(index); }
if (element == null) { element = GetElementFromElementFactory(index); } if (element == null) { element = GetElementFromElementFactory(index); }
@ -221,7 +221,7 @@ namespace Avalonia.Controls
return nextElement; return nextElement;
} }
public int GetElementIndex(VirtualizationInfo virtInfo) public int GetElementIndex(VirtualizationInfo? virtInfo)
{ {
if (virtInfo == null) if (virtInfo == null)
{ {
@ -627,11 +627,7 @@ namespace Avalonia.Controls
var element = GetElement(); var element = GetElement();
var virtInfo = ItemsRepeater.TryGetVirtualizationInfo(element); var virtInfo = ItemsRepeater.GetVirtualizationInfo(element);
if (virtInfo == null)
{
virtInfo = ItemsRepeater.CreateAndInitializeVirtualizationInfo(element);
}
// Clear flag // Clear flag
virtInfo.MustClearDataContext = false; virtInfo.MustClearDataContext = false;
@ -710,9 +706,8 @@ namespace Avalonia.Controls
{ {
if (parent is ItemsRepeater repeater) if (parent is ItemsRepeater repeater)
{ {
var element = child as Control;
if (repeater == owner && if (repeater == owner &&
element is not null && child is Control element &&
ItemsRepeater.GetVirtualizationInfo(element).IsRealized) ItemsRepeater.GetVirtualizationInfo(element).IsRealized)
{ {
focusedElement = element; focusedElement = element;
@ -722,7 +717,7 @@ namespace Avalonia.Controls
} }
child = parent; 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) if (Math.Abs(_expectedViewportShift.X) > 1 || Math.Abs(_expectedViewportShift.Y) > 1)
{ {
Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Expecting viewport shift of ({Shift})", 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 // 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 // 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) if (_pendingViewportShift.X != 0 || _pendingViewportShift.Y != 0)
{ {
Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Layout Updated with pending shift {Shift}- invalidating measure", Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Layout Updated with pending shift {Shift}- invalidating measure",
_owner.Layout.LayoutId, _owner.Layout?.LayoutId,
_pendingViewportShift); _pendingViewportShift);
// Assume this is never going to come. // Assume this is never going to come.
@ -436,7 +436,7 @@ namespace Avalonia.Controls
private void OnEffectiveViewportChanged(object? sender, EffectiveViewportChangedEventArgs e) 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); UpdateViewport(e.EffectiveViewport);
_pendingViewportShift = default; _pendingViewportShift = default;
@ -490,14 +490,14 @@ namespace Avalonia.Controls
var previousVisibleWindow = _visibleWindow; var previousVisibleWindow = _visibleWindow;
Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Effective Viewport: ({Before})->({After})", Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Effective Viewport: ({Before})->({After})",
_owner.Layout.LayoutId, _owner.Layout?.LayoutId,
previousVisibleWindow, previousVisibleWindow,
viewport); viewport);
if (-currentVisibleWindow.X <= ItemsRepeater.ClearedElementsArrangePosition.X && if (-currentVisibleWindow.X <= ItemsRepeater.ClearedElementsArrangePosition.X &&
-currentVisibleWindow.Y <= ItemsRepeater.ClearedElementsArrangePosition.Y) -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. // We got cleared.
_visibleWindow = default; _visibleWindow = default;
} }
@ -509,7 +509,7 @@ namespace Avalonia.Controls
if (_visibleWindow != previousVisibleWindow) if (_visibleWindow != previousVisibleWindow)
{ {
Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Used Viewport: ({Before})->({After})", Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Used Viewport: ({Before})->({After})",
_owner.Layout.LayoutId, _owner.Layout?.LayoutId,
previousVisibleWindow, previousVisibleWindow,
currentVisibleWindow); currentVisibleWindow);
TryInvalidateMeasure(); TryInvalidateMeasure();
@ -532,7 +532,7 @@ namespace Avalonia.Controls
// We invalidate measure instead of just invalidating arrange because // We invalidate measure instead of just invalidating arrange because
// we don't invalidate measure in UpdateViewport if the view is changing to // we don't invalidate measure in UpdateViewport if the view is changing to
// avoid layout cycles. // 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(); _owner.InvalidateMeasure();
} }
} }

Loading…
Cancel
Save