Browse Source

Fix realization of special elements.

We need to clear the scrollTo fields when they're taken for realization, otherwise we can get one element used in multiple positions.
pull/11455/head
Steven Kirk 3 years ago
parent
commit
48a79512a9
  1. 100
      src/Avalonia.Controls/VirtualizingStackPanel.cs

100
src/Avalonia.Controls/VirtualizingStackPanel.cs

@ -70,8 +70,8 @@ namespace Avalonia.Controls
private ScrollViewer? _scrollViewer; private ScrollViewer? _scrollViewer;
private Rect _viewport = s_invalidViewport; private Rect _viewport = s_invalidViewport;
private Dictionary<object, Stack<Control>>? _recyclePool; private Dictionary<object, Stack<Control>>? _recyclePool;
private Control? _outOfViewportFocusedElement; private Control? _focusedElement;
private int _outOfViewportFocusedIndex = -1; private int _focusedIndex = -1;
public VirtualizingStackPanel() public VirtualizingStackPanel()
{ {
@ -332,6 +332,10 @@ namespace Avalonia.Controls
{ {
if (index < 0 || index >= Items.Count) if (index < 0 || index >= Items.Count)
return null; return null;
if (_scrollToIndex == index)
return _scrollToElement;
if (_focusedIndex == index)
return _focusedElement;
if (GetRealizedElement(index) is { } realized) if (GetRealizedElement(index) is { } realized)
return realized; return realized;
if (Items[index] is Control c && c.GetValue(RecycleKeyProperty) == s_itemIsItsOwnContainer) if (Items[index] is Control c && c.GetValue(RecycleKeyProperty) == s_itemIsItsOwnContainer)
@ -343,8 +347,8 @@ namespace Avalonia.Controls
{ {
if (container == _scrollToElement) if (container == _scrollToElement)
return _scrollToIndex; return _scrollToIndex;
if (container == _outOfViewportFocusedElement) if (container == _focusedElement)
return _outOfViewportFocusedIndex; return _focusedIndex;
return _realizedElements?.GetIndex(container) ?? -1; return _realizedElements?.GetIndex(container) ?? -1;
} }
@ -364,16 +368,19 @@ namespace Avalonia.Controls
{ {
// Create and measure the element to be brought into view. Store it in a field so that // Create and measure the element to be brought into view. Store it in a field so that
// it can be re-used in the layout pass. // it can be re-used in the layout pass.
_scrollToElement = GetOrCreateElement(items, index); var scrollToElement = GetOrCreateElement(items, index);
_scrollToElement.Measure(Size.Infinity); scrollToElement.Measure(Size.Infinity);
_scrollToIndex = index;
// Get the expected position of the elment and put it in place. // Get the expected position of the elment and put it in place.
var anchorU = _realizedElements.GetOrEstimateElementU(index, ref _lastEstimatedElementSizeU); var anchorU = _realizedElements.GetOrEstimateElementU(index, ref _lastEstimatedElementSizeU);
var rect = Orientation == Orientation.Horizontal ? var rect = Orientation == Orientation.Horizontal ?
new Rect(anchorU, 0, _scrollToElement.DesiredSize.Width, _scrollToElement.DesiredSize.Height) : new Rect(anchorU, 0, scrollToElement.DesiredSize.Width, scrollToElement.DesiredSize.Height) :
new Rect(0, anchorU, _scrollToElement.DesiredSize.Width, _scrollToElement.DesiredSize.Height); new Rect(0, anchorU, scrollToElement.DesiredSize.Width, scrollToElement.DesiredSize.Height);
_scrollToElement.Arrange(rect); scrollToElement.Arrange(rect);
// Store the element and index so that they can be used in the layout pass.
_scrollToElement = scrollToElement;
_scrollToIndex = index;
// If the item being brought into view was added since the last layout pass then // If the item being brought into view was added since the last layout pass then
// our bounds won't be updated, so any containing scroll viewers will not have an // our bounds won't be updated, so any containing scroll viewers will not have an
@ -387,7 +394,7 @@ namespace Avalonia.Controls
} }
// Try to bring the item into view. // Try to bring the item into view.
_scrollToElement.BringIntoView(); scrollToElement.BringIntoView();
// If the viewport does not contain the item to scroll to, set _isWaitingForViewportUpdate: // If the viewport does not contain the item to scroll to, set _isWaitingForViewportUpdate:
// this should cause the following chain of events: // this should cause the following chain of events:
@ -406,10 +413,9 @@ namespace Avalonia.Controls
root.LayoutManager.ExecuteLayoutPass(); root.LayoutManager.ExecuteLayoutPass();
} }
var result = _scrollToElement;
_scrollToElement = null; _scrollToElement = null;
_scrollToIndex = -1; _scrollToIndex = -1;
return result; return scrollToElement;
} }
return null; return null;
@ -572,36 +578,48 @@ namespace Avalonia.Controls
{ {
Debug.Assert(ItemContainerGenerator is not null); Debug.Assert(ItemContainerGenerator is not null);
var e = GetRealizedElement(index); if ((GetRealizedElement(index) ??
GetRealizedElement(index, ref _focusedIndex, ref _focusedElement) ??
GetRealizedElement(index, ref _scrollToIndex, ref _scrollToElement)) is { } realized)
return realized;
if (e is null) var item = items[index];
{ var generator = ItemContainerGenerator!;
var item = items[index];
var generator = ItemContainerGenerator!;
if (generator.NeedsContainer(item, index, out var recycleKey)) if (generator.NeedsContainer(item, index, out var recycleKey))
{ {
e = GetRecycledElement(item, index, recycleKey) ?? return GetRecycledElement(item, index, recycleKey) ??
CreateElement(item, index, recycleKey); CreateElement(item, index, recycleKey);
} }
else else
{ {
e = GetItemAsOwnContainer(item, index); return GetItemAsOwnContainer(item, index);
}
} }
return e;
} }
private Control? GetRealizedElement(int index) private Control? GetRealizedElement(int index)
{ {
if (_scrollToIndex == index)
return _scrollToElement;
if (_outOfViewportFocusedIndex == index)
return _outOfViewportFocusedElement;
return _realizedElements?.GetElement(index); return _realizedElements?.GetElement(index);
} }
private static Control? GetRealizedElement(
int index,
ref int specialIndex,
ref Control? specialElement)
{
if (specialIndex == index)
{
Debug.Assert(specialElement is not null);
var result = specialElement;
specialIndex = -1;
specialElement = null;
return result;
}
return null;
}
private Control GetItemAsOwnContainer(object? item, int index) private Control GetItemAsOwnContainer(object? item, int index)
{ {
Debug.Assert(ItemContainerGenerator is not null); Debug.Assert(ItemContainerGenerator is not null);
@ -672,9 +690,9 @@ namespace Avalonia.Controls
} }
else if (element.IsKeyboardFocusWithin) else if (element.IsKeyboardFocusWithin)
{ {
_outOfViewportFocusedElement = element; _focusedElement = element;
_outOfViewportFocusedIndex = index; _focusedIndex = index;
_outOfViewportFocusedElement.LostFocus += OnUnrealizedFocusedElementLostFocus; _focusedElement.LostFocus += OnUnrealizedFocusedElementLostFocus;
} }
else else
{ {
@ -744,13 +762,13 @@ namespace Avalonia.Controls
private void OnUnrealizedFocusedElementLostFocus(object? sender, RoutedEventArgs e) private void OnUnrealizedFocusedElementLostFocus(object? sender, RoutedEventArgs e)
{ {
if (_outOfViewportFocusedElement is null || sender != _outOfViewportFocusedElement) if (_focusedElement is null || sender != _focusedElement)
return; return;
_outOfViewportFocusedElement.LostFocus -= OnUnrealizedFocusedElementLostFocus; _focusedElement.LostFocus -= OnUnrealizedFocusedElementLostFocus;
RecycleElement(_outOfViewportFocusedElement, _outOfViewportFocusedIndex); RecycleElement(_focusedElement, _focusedIndex);
_outOfViewportFocusedElement = null; _focusedElement = null;
_outOfViewportFocusedIndex = -1; _focusedIndex = -1;
} }
/// <inheritdoc/> /// <inheritdoc/>

Loading…
Cancel
Save