Browse Source

Allow selection of elements outside viewport.

Allow selection of elements outside viewport when using keyboard to select.
pull/11455/head
Steven Kirk 3 years ago
parent
commit
c4fe776b96
  1. 42
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  2. 31
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

42
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? _unrealizedFocusedElement; private Control? _outOfViewportFocusedElement;
private int _unrealizedFocusedIndex = -1; private int _outOfViewportFocusedIndex = -1;
public VirtualizingStackPanel() public VirtualizingStackPanel()
{ {
@ -332,14 +332,21 @@ namespace Avalonia.Controls
{ {
if (index < 0 || index >= Items.Count) if (index < 0 || index >= Items.Count)
return null; return null;
if (_realizedElements?.GetElement(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)
return c; return c;
return null; return null;
} }
protected internal override int IndexFromContainer(Control container) => _realizedElements?.GetIndex(container) ?? -1; protected internal override int IndexFromContainer(Control container)
{
if (container == _scrollToElement)
return _scrollToIndex;
if (container == _outOfViewportFocusedElement)
return _outOfViewportFocusedIndex;
return _realizedElements?.GetIndex(container) ?? -1;
}
protected internal override Control? ScrollIntoView(int index) protected internal override Control? ScrollIntoView(int index)
{ {
@ -590,6 +597,8 @@ namespace Avalonia.Controls
{ {
if (_scrollToIndex == index) if (_scrollToIndex == index)
return _scrollToElement; return _scrollToElement;
if (_outOfViewportFocusedIndex == index)
return _outOfViewportFocusedElement;
return _realizedElements?.GetElement(index); return _realizedElements?.GetElement(index);
} }
@ -621,15 +630,6 @@ namespace Avalonia.Controls
var generator = ItemContainerGenerator!; var generator = ItemContainerGenerator!;
if (_unrealizedFocusedIndex == index && _unrealizedFocusedElement is not null)
{
var element = _unrealizedFocusedElement;
_unrealizedFocusedElement.LostFocus -= OnUnrealizedFocusedElementLostFocus;
_unrealizedFocusedElement = null;
_unrealizedFocusedIndex = -1;
return element;
}
if (_recyclePool?.TryGetValue(recycleKey, out var recyclePool) == true && recyclePool.Count > 0) if (_recyclePool?.TryGetValue(recycleKey, out var recyclePool) == true && recyclePool.Count > 0)
{ {
var recycled = recyclePool.Pop(); var recycled = recyclePool.Pop();
@ -672,9 +672,9 @@ namespace Avalonia.Controls
} }
else if (element.IsKeyboardFocusWithin) else if (element.IsKeyboardFocusWithin)
{ {
_unrealizedFocusedElement = element; _outOfViewportFocusedElement = element;
_unrealizedFocusedIndex = index; _outOfViewportFocusedIndex = index;
_unrealizedFocusedElement.LostFocus += OnUnrealizedFocusedElementLostFocus; _outOfViewportFocusedElement.LostFocus += OnUnrealizedFocusedElementLostFocus;
} }
else else
{ {
@ -744,13 +744,13 @@ namespace Avalonia.Controls
private void OnUnrealizedFocusedElementLostFocus(object? sender, RoutedEventArgs e) private void OnUnrealizedFocusedElementLostFocus(object? sender, RoutedEventArgs e)
{ {
if (_unrealizedFocusedElement is null || sender != _unrealizedFocusedElement) if (_outOfViewportFocusedElement is null || sender != _outOfViewportFocusedElement)
return; return;
_unrealizedFocusedElement.LostFocus -= OnUnrealizedFocusedElementLostFocus; _outOfViewportFocusedElement.LostFocus -= OnUnrealizedFocusedElementLostFocus;
RecycleElement(_unrealizedFocusedElement, _unrealizedFocusedIndex); RecycleElement(_outOfViewportFocusedElement, _outOfViewportFocusedIndex);
_unrealizedFocusedElement = null; _outOfViewportFocusedElement = null;
_unrealizedFocusedIndex = -1; _outOfViewportFocusedIndex = -1;
} }
/// <inheritdoc/> /// <inheritdoc/>

31
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@ -893,6 +893,37 @@ namespace Avalonia.Controls.UnitTests
Assert.True(target.ContainerFromIndex(1).IsFocused); Assert.True(target.ContainerFromIndex(1).IsFocused);
} }
[Fact]
public void Down_Key_Brings_Unrealized_Selection_Into_View()
{
using var app = UnitTestApplication.Start(TestServices.RealFocus);
var items = Enumerable.Range(0, 100).Select(x => $"Item {x}").ToArray();
var target = new ListBox
{
Template = ListBoxTemplate(),
ItemsSource = items,
ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
Width = 100,
Height = 100,
SelectedIndex = 0,
};
Prepare(target);
target.ContainerFromIndex(0)!.Focus();
target.Scroll.Offset = new Vector(0, 100);
Layout(target);
var panel = (VirtualizingStackPanel)target.ItemsPanelRoot;
Assert.Equal(10, panel.FirstRealizedIndex);
RaiseKeyEvent(target, Key.Down);
Assert.Equal(1, target.SelectedIndex);
Assert.True(target.ContainerFromIndex(1).IsFocused);
Assert.Equal(new Vector(0, 10), target.Scroll.Offset);
}
[Fact] [Fact]
public void WrapSelection_Should_Wrap() public void WrapSelection_Should_Wrap()
{ {

Loading…
Cancel
Save