Browse Source

Fix tab navigation in ItemsControl & ListBox

- Don't set `KeyboardNavigationMode.Once` on `ItemsPresenter`
- Instead set it on `ListBox` (more controls to come)
- Make `TabOnceActiveElement` follow `Selection.AnchorIndex` in `SelectingItemsControl` and set it on `ItemsControl` itself
pull/11323/head
Steven Kirk 3 years ago
parent
commit
31ece204fc
  1. 3
      src/Avalonia.Controls/ListBox.cs
  2. 7
      src/Avalonia.Controls/Presenters/ItemsPresenter.cs
  3. 15
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  4. 121
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  5. 8
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

3
src/Avalonia.Controls/ListBox.cs

@ -58,6 +58,9 @@ namespace Avalonia.Controls
static ListBox()
{
ItemsPanelProperty.OverrideDefaultValue<ListBox>(DefaultPanel);
KeyboardNavigation.TabNavigationProperty.OverrideDefaultValue(
typeof(ListBox),
KeyboardNavigationMode.Once);
}
/// <summary>

7
src/Avalonia.Controls/Presenters/ItemsPresenter.cs

@ -52,13 +52,6 @@ namespace Avalonia.Controls.Presenters
nameof(VerticalSnapPointsChanged),
RoutingStrategies.Bubble);
static ItemsPresenter()
{
KeyboardNavigation.TabNavigationProperty.OverrideDefaultValue(
typeof(ItemsPresenter),
KeyboardNavigationMode.Once);
}
event EventHandler? ILogicalScrollable.ScrollInvalidated
{
add => _scrollInvalidated += value;

15
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -5,6 +5,7 @@ using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Xml.Linq;
using Avalonia.Controls.Selection;
using Avalonia.Controls.Utils;
using Avalonia.Data;
@ -528,13 +529,6 @@ namespace Avalonia.Controls.Primitives
protected internal override void ClearContainerForItemOverride(Control element)
{
base.ClearContainerForItemOverride(element);
if (Presenter?.Panel is InputElement panel &&
KeyboardNavigation.GetTabOnceActiveElement(panel) == element)
{
KeyboardNavigation.SetTabOnceActiveElement(panel, null);
}
element.ClearValue(IsSelectedProperty);
}
@ -834,12 +828,6 @@ namespace Avalonia.Controls.Primitives
Selection.Clear();
Selection.Select(index);
}
if (Presenter?.Panel is { } panel)
{
var container = ContainerFromIndex(index);
KeyboardNavigation.SetTabOnceActiveElement(panel, container);
}
}
/// <summary>
@ -928,6 +916,7 @@ namespace Avalonia.Controls.Primitives
if (e.PropertyName == nameof(ISelectionModel.AnchorIndex))
{
_hasScrolledToSelectedItem = false;
KeyboardNavigation.SetTabOnceActiveElement(this, ContainerFromIndex(Selection.AnchorIndex));
AutoScrollToSelectedItemIfNecessary();
}
else if (e.PropertyName == nameof(ISelectionModel.SelectedIndex) && _oldSelectedIndex != SelectedIndex)

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

@ -752,16 +752,6 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(Enumerable.Range(0, 10).Select(x => $"Item{10 - x}"), realized);
}
private static void RaiseKeyEvent(ListBox listBox, Key key, KeyModifiers inputModifiers = 0)
{
listBox.RaiseEvent(new KeyEventArgs
{
RoutedEvent = InputElement.KeyDownEvent,
KeyModifiers = inputModifiers,
Key = key
});
}
[Fact]
public void WrapSelection_Should_Wrap()
{
@ -948,6 +938,117 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(1, raised);
}
[Fact]
public void Tab_Navigation_Should_Move_To_First_Item_When_No_Anchor_Element_Selected()
{
var services = TestServices.StyledWindow.With(
focusManager: new FocusManager(),
keyboardDevice: () => new KeyboardDevice());
using var app = UnitTestApplication.Start(services);
var target = new ListBox
{
Template = ListBoxTemplate(),
Items = { "Foo", "Bar", "Baz" },
};
var button = new Button
{
Content = "Button",
[DockPanel.DockProperty] = Dock.Top,
};
var root = new TestRoot
{
Child = new DockPanel
{
Children =
{
button,
target,
}
}
};
var navigation = new KeyboardNavigationHandler();
navigation.SetOwner(root);
root.LayoutManager.ExecuteInitialLayoutPass();
button.Focus();
RaiseKeyEvent(button, Key.Tab);
var item = target.ContainerFromIndex(0);
Assert.Same(item, FocusManager.Instance.Current);
}
[Fact]
public void Tab_Navigation_Should_Move_To_Anchor_Element()
{
var services = TestServices.StyledWindow.With(
focusManager: new FocusManager(),
keyboardDevice: () => new KeyboardDevice());
using var app = UnitTestApplication.Start(services);
var target = new ListBox
{
Template = ListBoxTemplate(),
Items = { "Foo", "Bar", "Baz" },
};
var button = new Button
{
Content = "Button",
[DockPanel.DockProperty] = Dock.Top,
};
var root = new TestRoot
{
Width = 1000,
Height = 1000,
Child = new DockPanel
{
Children =
{
button,
target,
}
}
};
var navigation = new KeyboardNavigationHandler();
navigation.SetOwner(root);
root.LayoutManager.ExecuteInitialLayoutPass();
button.Focus();
target.Selection.AnchorIndex = 1;
RaiseKeyEvent(button, Key.Tab);
var item = target.ContainerFromIndex(1);
Assert.Same(item, FocusManager.Instance.Current);
RaiseKeyEvent(item, Key.Tab);
Assert.Same(button, FocusManager.Instance.Current);
target.Selection.AnchorIndex = 2;
RaiseKeyEvent(button, Key.Tab);
item = target.ContainerFromIndex(2);
Assert.Same(item, FocusManager.Instance.Current);
}
private static void RaiseKeyEvent(Control target, Key key, KeyModifiers inputModifiers = 0)
{
target.RaiseEvent(new KeyEventArgs
{
RoutedEvent = InputElement.KeyDownEvent,
KeyModifiers = inputModifiers,
Key = key
});
}
private record ItemViewModel(string Caption);
private class ResettingCollection : List<string>, INotifyCollectionChanged

8
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

@ -1236,13 +1236,13 @@ namespace Avalonia.Controls.UnitTests.Primitives
};
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
Prepare(target);
_helper.Down((Interactive)target.Presenter.Panel.Children[1]);
var container = target.ContainerFromIndex(1)!;
_helper.Down(container);
var panel = target.Presenter.Panel;
Assert.Equal(
KeyboardNavigation.GetTabOnceActiveElement((InputElement)panel),
panel.Children[1]);
Assert.Same(container, KeyboardNavigation.GetTabOnceActiveElement(target));
}
}

Loading…
Cancel
Save