瀏覽代碼

Implementing support for IsTabStop attached property

Luis von der Eltz 5 年之前
父節點
當前提交
c4ef08206e

+ 1 - 0
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 - 0
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);
+        }
     }
 }

+ 21 - 18
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 - 0
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()
         {