Browse Source

Register anchor candidate in panel.

We need to register controls as anchor candidates in the panel instead of in `ItemsControl` because the candidate needs to be registered after arrange. Consider this scenario:

- In Measure:
- Container is realized and registered as an anchor candidate
- Container is unrealized and unregistered
- Container is recycled and registered, but it is still placed in the position from before it was recycled
- In Arrange:
- The container is placed in its new position
- The `ScrollContentPresenter` sees it's been moved and adjusts the viewport to anchor it

This is obviously incorrect, but was what was happening when `ItemsControl` was responsible for registering anchor candidates.

Instead of tracking which containers have already been registered, change the list of anchor candidates in `ScrollContentPresenter` to a `HashSet` so we can just register it multiple times.
pull/10892/head
Steven Kirk 3 years ago
parent
commit
345fb7e1d6
  1. 4
      src/Avalonia.Controls/ItemsControl.cs
  2. 5
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  3. 16
      src/Avalonia.Controls/VirtualizingStackPanel.cs

4
src/Avalonia.Controls/ItemsControl.cs

@ -94,7 +94,6 @@ namespace Avalonia.Controls
private ItemContainerGenerator? _itemContainerGenerator;
private EventHandler<ChildIndexChangedEventArgs>? _childIndexChanged;
private IDataTemplate? _displayMemberItemTemplate;
private ScrollViewer? _scrollViewer;
private ItemsPresenter? _itemsPresenter;
/// <summary>
@ -457,7 +456,6 @@ namespace Avalonia.Controls
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_scrollViewer = e.NameScope.Find<ScrollViewer>("PART_ScrollViewer");
_itemsPresenter = e.NameScope.Find<ItemsPresenter>("PART_ItemsPresenter");
}
@ -629,7 +627,6 @@ namespace Avalonia.Controls
internal void ItemContainerPrepared(Control container, object? item, int index)
{
_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs(container, index));
_scrollViewer?.RegisterAnchorCandidate(container);
ContainerPrepared?.Invoke(this, new(container, index));
}
@ -642,7 +639,6 @@ namespace Avalonia.Controls
internal void ClearItemContainer(Control container)
{
_scrollViewer?.UnregisterAnchorCandidate(container);
ClearContainerForItemOverride(container);
ContainerClearing?.Invoke(this, new(container));
}

5
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@ -97,7 +97,7 @@ namespace Avalonia.Controls.Presenters
private Size _viewport;
private Dictionary<int, Vector>? _activeLogicalGestureScrolls;
private Dictionary<int, Vector>? _scrollGestureSnapPoints;
private List<Control>? _anchorCandidates;
private HashSet<Control>? _anchorCandidates;
private Control? _anchorElement;
private Rect _anchorElementBounds;
private bool _isAnchorElementDirty;
@ -310,7 +310,7 @@ namespace Avalonia.Controls.Presenters
"An anchor control must be a visual descendent of the ScrollContentPresenter.");
}
_anchorCandidates ??= new List<Control>();
_anchorCandidates ??= new();
_anchorCandidates.Add(element);
_isAnchorElementDirty = true;
}
@ -410,6 +410,7 @@ namespace Avalonia.Controls.Presenters
try
{
_arranging = true;
Offset = newOffset;
}
finally

16
src/Avalonia.Controls/VirtualizingStackPanel.cs

@ -67,6 +67,7 @@ namespace Avalonia.Controls
private double _lastEstimatedElementSizeU = 25;
private RealizedStackElements? _measureElements;
private RealizedStackElements? _realizedElements;
private ScrollViewer? _scrollViewer;
private Rect _viewport = s_invalidViewport;
private Stack<Control>? _recyclePool;
private Control? _unrealizedFocusedElement;
@ -203,6 +204,7 @@ namespace Avalonia.Controls
new Rect(u, 0, sizeU, finalSize.Height) :
new Rect(0, u, finalSize.Width, sizeU);
e.Arrange(rect);
_scrollViewer?.RegisterAnchorCandidate(e);
u += orientation == Orientation.Horizontal ? rect.Width : rect.Height;
}
}
@ -217,6 +219,18 @@ namespace Avalonia.Controls
}
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
_scrollViewer = this.FindAncestorOfType<ScrollViewer>();
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
_scrollViewer = null;
}
protected override void OnItemsChanged(IReadOnlyList<object?> items, NotifyCollectionChangedEventArgs e)
{
InvalidateMeasure();
@ -598,6 +612,8 @@ namespace Avalonia.Controls
{
Debug.Assert(ItemContainerGenerator is not null);
_scrollViewer?.UnregisterAnchorCandidate(element);
if (element.IsSet(ItemIsOwnContainerProperty))
{
element.IsVisible = false;

Loading…
Cancel
Save