diff --git a/samples/ControlCatalog/Pages/ButtonPage.xaml b/samples/ControlCatalog/Pages/ButtonPage.xaml
index 7e945aeaa9..8b697b7948 100644
--- a/samples/ControlCatalog/Pages/ButtonPage.xaml
+++ b/samples/ControlCatalog/Pages/ButtonPage.xaml
@@ -35,6 +35,7 @@
+
diff --git a/src/Avalonia.Input/KeyboardNavigation.cs b/src/Avalonia.Input/KeyboardNavigation.cs
index 28c0fe8df6..722215f8b7 100644
--- a/src/Avalonia.Input/KeyboardNavigation.cs
+++ b/src/Avalonia.Input/KeyboardNavigation.cs
@@ -30,6 +30,19 @@ namespace Avalonia.Input
"TabOnceActiveElement",
typeof(KeyboardNavigation));
+
+ ///
+ /// Defines the IsTabStop attached property.
+ ///
+ ///
+ /// The IsTabStop attached property determines whether the control is focusable by tab navigation.
+ ///
+ public static readonly AttachedProperty IsTabStopProperty =
+ AvaloniaProperty.RegisterAttached(
+ "IsTabStop",
+ typeof(KeyboardNavigation),
+ true);
+
///
/// Gets the for a container.
///
@@ -69,5 +82,25 @@ namespace Avalonia.Input
{
element.SetValue(TabOnceActiveElementProperty, value);
}
+
+ ///
+ /// Sets the for a container.
+ ///
+ /// The container.
+ /// Value indicating whether the container is a tab stop.
+ public static void SetIsTabStop(InputElement element, bool value)
+ {
+ element.SetValue(IsTabStopProperty, value);
+ }
+
+ ///
+ /// Gets the for a container.
+ ///
+ /// The container.
+ /// Whether the container is a tab stop.
+ public static bool GetIsTabStop(InputElement element)
+ {
+ return element.GetValue(IsTabStopProperty);
+ }
}
}
diff --git a/src/Avalonia.Input/Navigation/TabNavigation.cs b/src/Avalonia.Input/Navigation/TabNavigation.cs
index 596395eac6..dd50ea438a 100644
--- a/src/Avalonia.Input/Navigation/TabNavigation.cs
+++ b/src/Avalonia.Input/Navigation/TabNavigation.cs
@@ -77,7 +77,8 @@ namespace Avalonia.Input.Navigation
/// The element.
/// The tab direction. Must be Next or Previous.
/// The element's focusable descendants.
- private static IEnumerable GetFocusableDescendants(IInputElement element, NavigationDirection direction)
+ private static IEnumerable GetFocusableDescendants(IInputElement element,
+ NavigationDirection direction)
{
var mode = KeyboardNavigation.GetTabNavigation((InputElement)element);
@@ -113,7 +114,7 @@ namespace Avalonia.Input.Navigation
}
else
{
- if (child.CanFocus())
+ if (child.CanFocus() && KeyboardNavigation.GetIsTabStop((InputElement)child))
{
yield return child;
}
@@ -122,7 +123,10 @@ namespace Avalonia.Input.Navigation
{
foreach (var descendant in GetFocusableDescendants(child, direction))
{
- yield return descendant;
+ if (KeyboardNavigation.GetIsTabStop((InputElement)descendant))
+ {
+ yield return descendant;
+ }
}
}
}
@@ -167,7 +171,9 @@ namespace Avalonia.Input.Navigation
{
element = navigable.GetControl(direction, element, false);
- if (element != null && element.CanFocus())
+ if (element != null &&
+ element.CanFocus() &&
+ KeyboardNavigation.GetIsTabStop((InputElement) element))
{
break;
}
@@ -233,26 +239,22 @@ namespace Avalonia.Input.Navigation
return customNext.next;
}
- if (sibling.CanFocus())
+ if (sibling.CanFocus() && KeyboardNavigation.GetIsTabStop((InputElement) sibling))
{
return sibling;
}
- else
+
+ next = direction == NavigationDirection.Next ?
+ GetFocusableDescendants(sibling, direction).FirstOrDefault() :
+ GetFocusableDescendants(sibling, direction).LastOrDefault();
+
+ if (next != null)
{
- next = direction == NavigationDirection.Next ?
- GetFocusableDescendants(sibling, direction).FirstOrDefault() :
- GetFocusableDescendants(sibling, direction).LastOrDefault();
- if(next != null)
- {
- return next;
- }
+ return next;
}
}
- if (next == null)
- {
- next = GetFirstInNextContainer(element, parent, direction);
- }
+ next = GetFirstInNextContainer(element, parent, direction);
}
else
{
@@ -264,7 +266,8 @@ namespace Avalonia.Input.Navigation
return next;
}
- private static (bool handled, IInputElement next) GetCustomNext(IInputElement element, NavigationDirection direction)
+ private static (bool handled, IInputElement next) GetCustomNext(IInputElement element,
+ NavigationDirection direction)
{
if (element is ICustomKeyboardNavigation custom)
{
diff --git a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs
index 01f0ec247f..1efbbed2e8 100644
--- a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs
+++ b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs
@@ -185,6 +185,35 @@ namespace Avalonia.Input.UnitTests
Assert.Equal(next, result);
}
+ [Fact]
+ public void Next_Skips_Non_TabStop_Siblings()
+ {
+ Button current;
+ Button next;
+
+ var top = new StackPanel
+ {
+ Children =
+ {
+ new StackPanel
+ {
+ Children =
+ {
+ new Button { Name = "Button1" },
+ new Button { Name = "Button2" },
+ (current = new Button { Name = "Button3" }),
+ new Button { Name="Button4", [KeyboardNavigation.IsTabStopProperty] = false }
+ }
+ },
+ (next = new Button { Name = "Button5" }),
+ }
+ };
+
+ var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Next);
+
+ Assert.Equal(next, result);
+ }
+
[Fact]
public void Next_Continue_Returns_First_Control_In_Next_Uncle_Container()
{