diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index e2d2f0a516..7fdb75a9fc 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -330,31 +330,34 @@ namespace Avalonia.Controls /// The key events. protected override void OnKeyDown(KeyEventArgs e) { - var focus = FocusManager.Instance; - var direction = e.Key.ToNavigationDirection(); - var container = Presenter?.Panel as INavigableContainer; - - if (container == null || - focus.Current == null || - direction == null || - direction.Value.IsTab()) + if (!e.Handled) { - return; - } - - var current = focus.Current - .GetSelfAndVisualAncestors() - .OfType() - .FirstOrDefault(x => x.VisualParent == container); + var focus = FocusManager.Instance; + var direction = e.Key.ToNavigationDirection(); + var container = Presenter?.Panel as INavigableContainer; + + if (container == null || + focus.Current == null || + direction == null || + direction.Value.IsTab()) + { + return; + } - if (current != null) - { - var next = container.GetControl(direction.Value, current); + var current = focus.Current + .GetSelfAndVisualAncestors() + .OfType() + .FirstOrDefault(x => x.VisualParent == container); - if (next != null) + if (current != null) { - focus.Focus(next, NavigationMethod.Directional); - e.Handled = true; + var next = GetNextControl(container, direction.Value, current); + + if (next != null) + { + focus.Focus(next, NavigationMethod.Directional); + e.Handled = true; + } } } @@ -498,5 +501,27 @@ namespace Avalonia.Controls ItemCount = Items.Count(); } } + + protected static IInputElement GetNextControl( + INavigableContainer container, + NavigationDirection direction, + IInputElement from) + { + IInputElement result; + + while (from != null) + { + result = container.GetControl(direction, from); + + if (result?.Focusable == true) + { + return result; + } + + from = result; + } + + return null; + } } } diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs index 9ef1e9f0d2..3cf886ade4 100644 --- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs @@ -11,6 +11,7 @@ using Avalonia.VisualTree; using Xunit; using System.Collections.ObjectModel; using Avalonia.UnitTests; +using Avalonia.Input; namespace Avalonia.Controls.UnitTests { @@ -494,6 +495,77 @@ namespace Avalonia.Controls.UnitTests Assert.NotNull(NameScope.GetNameScope((TextBlock)container.Child)); } + [Fact] + public void Focuses_Next_Item_On_Key_Down() + { + using (UnitTestApplication.Start(TestServices.RealFocus)) + { + var items = new object[] + { + new Button(), + new Button(), + }; + + var target = new ItemsControl + { + Template = GetTemplate(), + Items = items, + }; + + var root = new TestRoot { Child = target }; + + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + target.Presenter.Panel.Children[0].Focus(); + + target.RaiseEvent(new KeyEventArgs + { + RoutedEvent = InputElement.KeyDownEvent, + Key = Key.Down, + }); + + Assert.Equal( + target.Presenter.Panel.Children[1], + FocusManager.Instance.Current); + } + } + + [Fact] + public void Does_Not_Focus_Non_Focusable_Item_On_Key_Down() + { + using (UnitTestApplication.Start(TestServices.RealFocus)) + { + var items = new object[] + { + new Button(), + new Button { Focusable = false }, + new Button(), + }; + + var target = new ItemsControl + { + Template = GetTemplate(), + Items = items, + }; + + var root = new TestRoot { Child = target }; + + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + target.Presenter.Panel.Children[0].Focus(); + + target.RaiseEvent(new KeyEventArgs + { + RoutedEvent = InputElement.KeyDownEvent, + Key = Key.Down, + }); + + Assert.Equal( + target.Presenter.Panel.Children[2], + FocusManager.Instance.Current); + } + } + private class Item { public Item(string value)