diff --git a/src/Avalonia.Controls/VirtualizingPanel.cs b/src/Avalonia.Controls/VirtualizingPanel.cs
index dd40f868d5..87c630658f 100644
--- a/src/Avalonia.Controls/VirtualizingPanel.cs
+++ b/src/Avalonia.Controls/VirtualizingPanel.cs
@@ -3,13 +3,14 @@ using System.Collections;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Controls.Utils;
+using Avalonia.Input;
namespace Avalonia.Controls
{
///
/// Base class for panels that can be used to virtualize items.
///
- public abstract class VirtualizingPanel : Panel
+ public abstract class VirtualizingPanel : Panel, INavigableContainer
{
private ItemsControl? _itemsControl;
@@ -27,11 +28,19 @@ namespace Avalonia.Controls
}
}
+ IInputElement? INavigableContainer.GetControl(NavigationDirection direction, IInputElement? from, bool wrap)
+ {
+ return GetControl(direction, from, wrap);
+ }
+
///
/// Scrolls the specified item into view.
///
/// The index of the item.
- protected internal abstract void ScrollIntoView(int index);
+ ///
+ /// The element with the specified index, or null if the element could not be brought into view.
+ ///
+ protected internal abstract Control? ScrollIntoView(int index);
///
/// Returns the container for the item at the specified index.
@@ -53,6 +62,15 @@ namespace Avalonia.Controls
///
protected internal abstract int IndexFromContainer(Control container);
+ ///
+ /// Gets the next control in the specified direction.
+ ///
+ /// The movement direction.
+ /// The control from which movement begins.
+ /// Whether to wrap around when the first or last item is reached.
+ /// The control.
+ protected abstract IInputElement? GetControl(NavigationDirection direction, IInputElement? from, bool wrap);
+
///
/// Called when the that owns the panel changes.
///
diff --git a/src/Avalonia.Controls/VirtualizingStackPanel.cs b/src/Avalonia.Controls/VirtualizingStackPanel.cs
index a4e287dc08..2e80edd6bb 100644
--- a/src/Avalonia.Controls/VirtualizingStackPanel.cs
+++ b/src/Avalonia.Controls/VirtualizingStackPanel.cs
@@ -3,8 +3,8 @@ using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
-using System.Xml.Linq;
using Avalonia.Controls.Utils;
+using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Utilities;
using Avalonia.VisualTree;
@@ -169,19 +169,79 @@ namespace Avalonia.Controls
InvalidateMeasure();
}
+ protected override IInputElement? GetControl(NavigationDirection direction, IInputElement? from, bool wrap)
+ {
+ var count = (ItemsControl?.Items as IList)?.Count ?? 0;
+
+ if (count == 0)
+ return null;
+
+ var horiz = Orientation == Orientation.Horizontal;
+ var fromIndex = from != null ? Children.IndexOf(from) : -1;
+ var toIndex = fromIndex;
+
+ switch (direction)
+ {
+ case NavigationDirection.First:
+ toIndex = 0;
+ break;
+ case NavigationDirection.Last:
+ toIndex = count - 1;
+ break;
+ case NavigationDirection.Next:
+ ++toIndex;
+ break;
+ case NavigationDirection.Previous:
+ --toIndex;
+ break;
+ case NavigationDirection.Left:
+ if (horiz)
+ --toIndex;
+ break;
+ case NavigationDirection.Right:
+ if (horiz)
+ ++toIndex;
+ break;
+ case NavigationDirection.Up:
+ if (!horiz)
+ --toIndex;
+ break;
+ case NavigationDirection.Down:
+ if (!horiz)
+ ++toIndex;
+ break;
+ default:
+ return null;
+ }
+
+ if (fromIndex == toIndex)
+ return from;
+
+ if (wrap)
+ {
+ if (toIndex < 0)
+ toIndex = count - 1;
+ else if (toIndex >= count - 1)
+ toIndex = 0;
+ }
+
+ return ScrollIntoView(toIndex);
+ }
+
protected internal override Control? ContainerFromIndex(int index) => _realizedElements?.GetElement(index);
protected internal override int IndexFromContainer(Control container) => _realizedElements?.GetIndex(container) ?? -1;
- protected internal override void ScrollIntoView(int index)
+ protected internal override Control? ScrollIntoView(int index)
{
var items = ItemsControl?.Items as IList;
if (_isInLayout || items is null || index < 0 || index >= items.Count)
- return;
+ return null;
if (GetRealizedElement(index) is Control element)
{
element.BringIntoView();
+ return element;
}
else if (this.GetVisualRoot() is ILayoutRoot root)
{
@@ -216,9 +276,13 @@ namespace Avalonia.Controls
root.LayoutManager.ExecuteLayoutPass();
_isWaitingForViewportUpdate = false;
+ var result = _anchorElement;
_anchorElement = null;
_anchorIndex = -1;
+ return result;
}
+
+ return null;
}
internal IReadOnlyList GetRealizedElements()