键盘和辅助功能工具的焦点导航

使用焦点导航可以在你的 Windows 应用中提供全面且一致的交互体验,并可为使用键盘的超级用户、残障人士及具有其他辅助功能要求的用户提供自定义控件,还可提供 10 英尺电视屏幕和 Xbox One 体验。

概述

焦点导航是指使用户能够使用键盘、游戏板或远程控件导航和与 Windows 应用程序的 UI 进行交互的基础机制。

注释

输入设备通常归类为指向设备,例如触摸、触摸板、笔和鼠标,以及非指向设备,例如键盘、游戏板和远程控制。

本主题介绍如何优化 Windows 应用程序,并为依赖非点输入类型的用户生成自定义交互体验。

尽管我们专注于 PC 上 Windows 应用中自定义控件的键盘输入,但设计良好的键盘体验对于软件键盘(如触摸键盘和屏幕键盘(OSK))也很重要,这样可以支持 Windows 讲述人等辅助功能工具,并支持 10 英尺用户体验。

有关在 Windows 应用程序中生成用于指向设备的自定义体验的指导,请参阅 Handle 指针输入 。

有关生成键盘应用和体验的更常规信息,请参阅 键盘交互。

通用指南

只有需要用户交互的 UI 元素应支持焦点导航,不需要作的元素(如静态图像)不需要键盘焦点。 屏幕阅读器和类似的辅助功能工具仍会报出这些静态元素,即使它们不包含在焦点导航中也是如此。

请务必记住,与使用指针设备(如鼠标或触摸)导航不同,焦点导航是线性的。 实现焦点导航时,请考虑用户如何与应用程序交互以及逻辑导航应是什么。 在大多数情况下,我们建议自定义的焦点导航行为遵循用户文化的首选阅读模式。

其他一些重点导航注意事项包括:

  • 控件是否按逻辑分组?
  • 是否有具有更大重要性的控件组?
    • 如果是,这些组是否包含子组?
  • 布局是否需要自定义方向导航(箭头键)和 Tab 键顺序?

辅助功能工程软件电子书具有关于设计逻辑层次结构的优秀章节。

键盘的 2D 方向导航

控件或控件组的 2D 内部导航区域称为其“方向区域”。 当焦点移动到此对象时,键盘箭头键(向左、向右、向上和向下)可用于在方向区域中的子元素之间导航。

控制组的方向区域 控制组的二维内部导航区域或方向区域

可以使用 XYFocusKeyboardNavigation 属性(具有“ 自动”、“ 已启用”或 “已禁用”的可能值)通过键盘箭头键管理 2D 内部导航。

注释

Tab 顺序不受此属性影响。 为了避免导航体验混乱,我们建议在应用程序的 Tab 键导航顺序中不要显式指定方向区域的子元素。 有关元素的制表符行为的详细信息,请参阅 UIElement.TabFocusNavigation 和 TabIndex 属性。

自动 (默认行为)

设置为“自动”时,方向导航行为由元素的祖先或继承层次结构决定。 如果所有上级都处于默认模式(设置为“自动”),则不支持使用键盘进行方向导航。

已禁用

将 XYFocusKeyboardNavigation 设置为 Disabled 以阻止指向控件及其子元素的方向导航。

XYFocusKeyboardNavigation 禁用行为 XYFocusKeyboardNavigation 禁用行为

在此示例中,主 StackPanel (ContainerPrimary) 已将 XYFocusKeyboardNavigation 设置为 Enabled。 所有子元素都继承此设置,可以使用箭头键导航到该设置。 但是,B3 和 B4 元素位于辅助 StackPanel (ContainerSecondary)中, XYFocusKeyboardNavigation 设置为 Disabled,这会替代主容器,并禁用指向自身及其子元素之间的箭头键导航。

<Grid 
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" 
    TabFocusNavigation="Cycle">
    <Grid.RowDefinitions>
        <RowDefinition Height="40"/>
        <RowDefinition Height="75"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <TextBlock Name="KeyPressed"
                Grid.Row="0" 
                FontWeight="ExtraBold" 
                HorizontalTextAlignment="Center"
                TextWrapping="Wrap" 
                Padding="10" />
    <StackPanel Name="ContainerPrimary" 
                XYFocusKeyboardNavigation="Enabled" 
                KeyDown="ContainerPrimary_KeyDown" 
                Orientation="Horizontal" 
                BorderBrush="Green" 
                BorderThickness="2" 
                Grid.Row="1" 
                Padding="10" 
                MaxWidth="200">
        <Button Name="B1" 
                Content="B1" 
                GettingFocus="Btn_GettingFocus" />
        <Button Name="B2" 
                Content="B2" 
                GettingFocus="Btn_GettingFocus" />
        <StackPanel Name="ContainerSecondary" 
                    XYFocusKeyboardNavigation="Disabled" 
                    Orientation="Horizontal" 
                    BorderBrush="Red" 
                    BorderThickness="2">
            <Button Name="B3" 
                    Content="B3" 
                    GettingFocus="Btn_GettingFocus" />
            <Button Name="B4" 
                    Content="B4" 
                    GettingFocus="Btn_GettingFocus" />
        </StackPanel>
    </StackPanel>
</Grid>

已启用

将 XYFocusKeyboardNavigation 设置为 Enabled 以支持指向控件及其每个 UIElement 子对象的 2D 方向导航。

设置后,使用箭头键的导航仅限于方向区域中的元素。 选项卡导航不受影响,因为所有控件仍可通过其 Tab 键顺序层次结构进行访问。

启用后的 XYFocusKeyboardNavigation 行为 启用的 XYFocusKeyboardNavigation 行为

在此示例中,主 StackPanel (ContainerPrimary) 已将 XYFocusKeyboardNavigation 设置为 Enabled。 所有子元素都继承此设置,可以使用箭头键导航到该设置。 B3 和 B4 元素位于一个辅助 StackPanel (ContainerSecondary),此处未设置 XYFocusKeyboardNavigation,因此继承了主容器的设置。 B5 元素不在声明的方向区域中,不支持箭头键导航,但支持标准选项卡导航行为。

<Grid
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    TabFocusNavigation="Cycle">
    <Grid.RowDefinitions>
        <RowDefinition Height="40"/>
        <RowDefinition Height="100"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <TextBlock Name="KeyPressed"
               Grid.Row="0"
               FontWeight="ExtraBold"
               HorizontalTextAlignment="Center"
               TextWrapping="Wrap"
               Padding="10" />
    <StackPanel Grid.Row="1"
                Orientation="Horizontal"
                HorizontalAlignment="Center">
        <StackPanel Name="ContainerPrimary"
                    XYFocusKeyboardNavigation="Enabled"
                    KeyDown="ContainerPrimary_KeyDown"
                    Orientation="Horizontal"
                    BorderBrush="Green"
                    BorderThickness="2"
                    Padding="5" Margin="5">
            <Button Name="B1"
                    Content="B1"
                    GettingFocus="Btn_GettingFocus" Margin="5" />
            <Button Name="B2"
                    Content="B2"
                    GettingFocus="Btn_GettingFocus" />
            <StackPanel Name="ContainerSecondary"
                        Orientation="Horizontal"
                        BorderBrush="Red"
                        BorderThickness="2"
                        Margin="5">
                <Button Name="B3"
                        Content="B3"
                        GettingFocus="Btn_GettingFocus"
                        Margin="5" />
                <Button Name="B4"
                        Content="B4"
                        GettingFocus="Btn_GettingFocus"
                        Margin="5" />
            </StackPanel>
        </StackPanel>
        <Button Name="B5"
                Content="B5"
                GettingFocus="Btn_GettingFocus"
                Margin="5" />
    </StackPanel>
</Grid>

您可以拥有多个级别的嵌套定向区域。 如果所有父元素都已将 XYFocusKeyboardNavigation 设置为 Enabled,则忽略内部导航区域边界。

下面是一个不显式支持 2D 方向导航的元素中的两个嵌套方向区域的示例。 在这种情况下,两个嵌套区域之间不支持方向导航。

XYFocusKeyboardNavigation 已启用和嵌套行为 XYFocusKeyboardNavigation 已启用和嵌套行为

下面是三个嵌套方向区域的更复杂的示例,其中:

  • 当 B1 具有焦点时,只能导航到 B5(反之亦然),因为存在一个方向区域边界,其中 XYFocusKeyboardNavigation 设置为“已禁用”,从而使 B2、B3 和 B4 无法使用箭头键访问
  • 当 B2 具有焦点时,只能导航到 B3(反之亦然),因为方向区域边界阻止箭头键导航到 B1、B4 和 B5
  • 当 B4 具有焦点时,必须使用 Tab 键在控件之间导航

XYFocusKeyboardNavigation 已启用,并具有复杂的嵌套行为

XYFocusKeyboardNavigation 已启用和复杂的嵌套行为

选项卡导航

虽然箭头键可用于控件或控件组的 2D 方向导航,但 Tab 键可用于在 Windows 应用程序中的所有控件之间导航。

默认情况下,所有交互式控件都支持 Tab 键导航(IsEnabled 和 IsTabStop 属性为 true),并且逻辑选项卡顺序派生自应用程序中的控件布局。 但是,默认顺序不一定对应于视觉顺序。 实际显示位置可能取决于父布局容器以及可对子元素设置的某些属性来影响布局。

避免使用自定义选项卡顺序,使焦点在应用程序中四处跳转。 例如,窗体中的控件列表应具有从上到下和从左到右(具体取决于区域设置)的 Tab 键顺序。

在本部分中,我们将介绍如何完全自定义此选项卡顺序以适应你的应用。

设置选项卡导航行为

UIElement 的 TabFocusNavigation 属性指定其整个对象树(或方向区域)的选项卡导航行为。

注释

对于不使用 ControlTemplate 定义其外观的对象,请使用此属性而不是 Control.TabNavigation 属性。

如上一部分所述,为了避免导航体验混乱,我们建议在应用程序的 Tab 键导航顺序中不要显式指定方向区域的子元素。 有关元素的制表符行为的详细信息,请参阅 UIElement.TabFocusNavigation 和 TabIndex 属性。

对于早于 Windows 10 Creators Update(内部版本 10.0.15063)的版本,选项卡设置仅限于 ControlTemplate 对象。 有关详细信息,请参阅 Control.TabNavigation。

TabFocusNavigation 是一个具有 KeyboardNavigationMode 类型及以下可能值的属性(请注意,这些示例不是自定义控件组,因此不需要使用箭头键进行内部导航):

  • 本地 (默认值) 选项卡索引在容器内的本地子树上识别。 对于此示例,Tab 顺序为 B1、B2、B3、B4、B5、B6、B7、B1。

    “本地”选项卡导航行为

    “本地”选项卡导航行为

  • 一次 容器和所有子元素接收焦点一次。 对于此示例,选项卡顺序为 B1、B2、B7、B1(还演示了带有箭头键的内部导航)。

    “单次”选项卡导航行为

    “Once” 选项卡导航行为

  • Cycle
    焦点循环回到容器内的初始可聚焦元素。 对于此示例,选项卡顺序为 B1、B2、B3、B4、B5、B6、B2...

    “循环”选项卡导航行为

    “循环”选项卡导航行为

下面是上述示例的代码(使用 TabFocusNavigation =“Cycle)。

<Grid 
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" 
    TabFocusNavigation="Cycle">
    <Grid.RowDefinitions>
        <RowDefinition Height="40"/>
        <RowDefinition Height="300"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <TextBlock Name="KeyPressed"
               Grid.Row="0" 
               FontWeight="ExtraBold" 
               HorizontalTextAlignment="Center"
               TextWrapping="Wrap" 
               Padding="10" />
    <StackPanel Name="ContainerPrimary"
                KeyDown="Container_KeyDown" 
                Orientation="Horizontal" 
                HorizontalAlignment="Center"
                BorderBrush="Green" 
                BorderThickness="2" 
                Grid.Row="1" 
                Padding="10" 
                MaxWidth="200">
        <Button Name="B1" 
                Content="B1" 
                GettingFocus="Btn_GettingFocus" 
                Margin="5"/>
        <StackPanel Name="ContainerSecondary" 
                    KeyDown="Container_KeyDown"
                    XYFocusKeyboardNavigation="Enabled" 
                    TabFocusNavigation ="Cycle"
                    Orientation="Vertical" 
                    VerticalAlignment="Center"
                    BorderBrush="Red" 
                    BorderThickness="2"
                    Padding="5" Margin="5">
            <Button Name="B2" 
                    Content="B2" 
                    GettingFocus="Btn_GettingFocus" 
                    Margin="5"/>
            <Button Name="B3" 
                    Content="B3" 
                    GettingFocus="Btn_GettingFocus" 
                    Margin="5"/>
            <Button Name="B4" 
                    Content="B4" 
                    GettingFocus="Btn_GettingFocus" 
                    Margin="5"/>
            <Button Name="B5" 
                    Content="B5" 
                    GettingFocus="Btn_GettingFocus" 
                    Margin="5"/>
            <Button Name="B6" 
                    Content="B6" 
                    GettingFocus="Btn_GettingFocus" 
                    Margin="5"/>
        </StackPanel>
        <Button Name="B7" 
                Content="B7" 
                GettingFocus="Btn_GettingFocus" 
                Margin="5"/>
    </StackPanel>
</Grid>

TabIndex

使用 TabIndex 指定用户在使用 Tab 键浏览控件时,元素接收焦点的顺序。 具有较低选项卡索引的控件在具有较高索引的控件之前接收焦点。

如果控件未指定 TabIndex ,则会根据范围为其分配比可视化树中所有交互式控件的当前最高索引值(和最低优先级)更高的索引值。

控件的所有子元素都被视为范围,如果其中一个元素也具有子元素,则它们被视为另一个范围。 通过选择范围可视化树上的第一个元素来解决任何歧义。

若要从 Tab 顺序中排除控件,请将 IsTabStop 属性设置为 false。

通过设置 TabIndex 属性替代默认的 Tab 键顺序。

注释

TabIndex 的工作方式与 UIElement.TabFocusNavigation 和 Control.TabNavigation 的工作方式相同。

在这里,我们将展示焦点导航如何受特定元素上的 TabIndex 属性的影响。

使用 TabIndex 行为的“本地”选项卡导航

使用 TabIndex 行为的“本地”Tab 键导航

在前面的示例中,有两个范围:

  • B1、方向区域(B2 - B6)和 B7
  • 方向区域 (B2 - B6)

当 B3(在方向区域中)获得焦点时,范围将更改,并且 Tab 键导航将转移到已识别后续焦点的最佳候选项的方向区域。 在这种情况下,B2 后接 B4、B5 和 B6。 然后,作用域会再次更改,焦点将移动到 B1。

下面是此示例的代码。

<Grid
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    TabFocusNavigation="Cycle">
    <Grid.RowDefinitions>
        <RowDefinition Height="40"/>
        <RowDefinition Height="300"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <TextBlock Name="KeyPressed"
               Grid.Row="0"
               FontWeight="ExtraBold"
               HorizontalTextAlignment="Center"
               TextWrapping="Wrap"
               Padding="10" />
    <StackPanel Name="ContainerPrimary"
                KeyDown="Container_KeyDown"
                Orientation="Horizontal"
                HorizontalAlignment="Center"
                BorderBrush="Green"
                BorderThickness="2"
                Grid.Row="1"
                Padding="10"
                MaxWidth="200">
        <Button Name="B1"
                Content="B1"
                TabIndex="1"
                ToolTipService.ToolTip="TabIndex = 1"
                GettingFocus="Btn_GettingFocus"
                Margin="5"/>
        <StackPanel Name="ContainerSecondary"
                    KeyDown="Container_KeyDown"
                    TabFocusNavigation ="Local"
                    Orientation="Vertical"
                    VerticalAlignment="Center"
                    BorderBrush="Red"
                    BorderThickness="2"
                    Padding="5" Margin="5">
            <Button Name="B2"
                    Content="B2"
                    GettingFocus="Btn_GettingFocus"
                    Margin="5"/>
            <Button Name="B3"
                    Content="B3"
                    TabIndex="3"
                    ToolTipService.ToolTip="TabIndex = 3"
                    GettingFocus="Btn_GettingFocus"
                    Margin="5"/>
            <Button Name="B4"
                    Content="B4"
                    GettingFocus="Btn_GettingFocus"
                    Margin="5"/>
            <Button Name="B5"
                    Content="B5"
                    GettingFocus="Btn_GettingFocus"
                    Margin="5"/>
            <Button Name="B6"
                    Content="B6"
                    GettingFocus="Btn_GettingFocus"
                    Margin="5"/>
        </StackPanel>
        <Button Name="B7"
                Content="B7"
                TabIndex="2"
                ToolTipService.ToolTip="TabIndex = 2"
                GettingFocus="Btn_GettingFocus"
                Margin="5"/>
    </StackPanel>
</Grid>

键盘、游戏板和遥控器的 2D 方向导航

非指针输入类型(如键盘、游戏板、远程控制和辅助功能工具(如 Windows 讲述人)共享一种通用的基础机制,用于导航和与 Windows 应用程序的 UI 交互。

在本部分中,我们将介绍如何通过一组支持基于焦点的非指针输入类型的导航策略属性来指定首选导航策略并微调应用程序中的焦点导航。

有关为 Xbox/TV 生成应用和体验的更常规信息,请参阅 键盘交互、 Xbox 和电视设计以及 游戏板和远程控制交互。

导航策略适用于键盘、游戏板、远程控制和各种辅助功能工具。

利用以下导航策略属性,可根据箭头键、方向键按钮或按下的类似键影响将获得焦点的控件。

  • XYFocusUpNavigationStrategy
  • XYFocusDownNavigationStrategy
  • XYFocusLeftNavigationStrategy
  • XYFocusRightNavigationStrategy

这些属性的可能值为 Auto (default)、 NavigationDirectionDistance、 投影或 RectilinearDistance 。

如果设置为 “自动”,则元素的行为基于元素的上级。 如果所有元素都设置为 “自动”,则使用 投影 。

注释

其他因素(如以前聚焦的元素或与导航方向轴的邻近度)可能会影响结果。

投影

在当前聚焦元素的边缘沿导航方向投影时,投影策略会将焦点移到第一个遇到的元素。

在此示例中,每个焦点导航方向都设置为投影。 请注意焦点如何从 B1 移动到 B4,绕过 B3。 这是因为 B3 不在投影区域中。 另请注意从 B1 左移时为何无法识别焦点候选项。 因为 B2 相对于 B1 的位置,排除了 B3 作为候选项的可能性。 如果 B3 与 B2 位于同一行中,则它是左侧导航的可行候选项。 由于与导航方向轴的距离近且无障碍,因此 B2 是可行候选项。

投影导航策略

投影导航策略

NavigationDirectionDistance 策略将焦点移动到最接近导航方向轴的元素。

导航方向相对应的边界矩形的边缘会被扩展并投影,以识别候选目标。 遇到的第一个元素被确定为目标。 对于多个候选项,最接近的元素被标识为目标。 如果仍有多个候选项,则最顶层/最左边的元素被标识为候选项。

NavigationDirectionDistance 导航策略

NavigationDirectionDistance 导航策略

RectilinearDistance

RectilinearDistance 策略根据 2D 直线距离(Taxicab 几何图形)将焦点移到距离最近的元素。

每个潜在候选项的主距离与次要距离之和都可用于识别最佳候选项。 在平局中,如果请求的方向是向上或向下,则选择左侧的第一个元素,如果请求的方向为左或向右,则选择顶部的第一个元素。

RectilinearDistance 导航策略

RectilinearDistance 导航策略

此图表明为何当 B1 具有焦点且向下是请求的方向时,B3 是 RectilinearDistance 的焦点候选项。 这基于此示例的以下计算:

  • 距离 (B1, B3, 向下) 为 10 + 0 = 10
  • 距离 (B1, B2, 向下) 为 0 + 40 = 30
  • 距离 (B1, D, 向下) 为 30 + 0 = 30