Browse Source

Implementing support for IsTabStop attached property

pull/4162/head
Luis von der Eltz 6 years ago
parent
commit
c4ef08206e
  1. 1
      samples/ControlCatalog/Pages/ButtonPage.xaml
  2. 33
      src/Avalonia.Input/KeyboardNavigation.cs
  3. 39
      src/Avalonia.Input/Navigation/TabNavigation.cs
  4. 29
      tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs

1
samples/ControlCatalog/Pages/ButtonPage.xaml

@ -35,6 +35,7 @@
<Button BorderBrush="{DynamicResource ThemeAccentBrush}">Border Color</Button>
<Button BorderBrush="{DynamicResource ThemeAccentBrush}" BorderThickness="4">Thick Border</Button>
<Button BorderBrush="{DynamicResource ThemeAccentBrush}" BorderThickness="4" IsEnabled="False">Disabled</Button>
<Button BorderBrush="{DynamicResource ThemeAccentBrush}" KeyboardNavigation.IsTabStop="False">IsTabStop=False</Button>
</StackPanel>
</StackPanel>
</StackPanel>

33
src/Avalonia.Input/KeyboardNavigation.cs

@ -30,6 +30,19 @@ namespace Avalonia.Input
"TabOnceActiveElement",
typeof(KeyboardNavigation));
/// <summary>
/// Defines the IsTabStop attached property.
/// </summary>
/// <remarks>
/// The IsTabStop attached property determines whether the control is focusable by tab navigation.
/// </remarks>
public static readonly AttachedProperty<bool> IsTabStopProperty =
AvaloniaProperty.RegisterAttached<InputElement, bool>(
"IsTabStop",
typeof(KeyboardNavigation),
true);
/// <summary>
/// Gets the <see cref="TabNavigationProperty"/> for a container.
/// </summary>
@ -69,5 +82,25 @@ namespace Avalonia.Input
{
element.SetValue(TabOnceActiveElementProperty, value);
}
/// <summary>
/// Sets the <see cref="IsTabStopProperty"/> for a container.
/// </summary>
/// <param name="element">The container.</param>
/// <param name="value">Value indicating whether the container is a tab stop.</param>
public static void SetIsTabStop(InputElement element, bool value)
{
element.SetValue(IsTabStopProperty, value);
}
/// <summary>
/// Gets the <see cref="IsTabStopProperty"/> for a container.
/// </summary>
/// <param name="element">The container.</param>
/// <returns>Whether the container is a tab stop.</returns>
public static bool GetIsTabStop(InputElement element)
{
return element.GetValue(IsTabStopProperty);
}
}
}

39
src/Avalonia.Input/Navigation/TabNavigation.cs

@ -77,7 +77,8 @@ namespace Avalonia.Input.Navigation
/// <param name="element">The element.</param>
/// <param name="direction">The tab direction. Must be Next or Previous.</param>
/// <returns>The element's focusable descendants.</returns>
private static IEnumerable<IInputElement> GetFocusableDescendants(IInputElement element, NavigationDirection direction)
private static IEnumerable<IInputElement> 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)
{

29
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()
{

Loading…
Cancel
Save