适用于键盘、手柄、遥控器和辅助功能工具的焦点导航

键盘、遥控器和方向键

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

概述

焦点导航指的是可以让用户使用键盘、手柄或遥控器进行导航并与 Windows 应用程序的 UI 进行交互的基础机制。

注意

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

本主题介绍如何优化 Windows 应用程序并为依赖于非指针输入类型的用户构建自定义交互体验。

虽然我们对电脑上 Windows 应用中的自定义控件侧重于键盘输入,但精心设计的键盘体验对于触摸键盘和屏幕键盘 (OSK) 等支持辅助功能工具(例如 Windows 讲述人)并支持 10 英尺体验的软件键盘也很重要。

有关在适用于指针设备的 Windows 应用程序中构建自定义体验的指导,请参阅处理指针输入

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

通用指南

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

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

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

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

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

键盘的 2D 方向导航

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

方向区域控件组的 2D 内部导航区域或方向区域

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

注意

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

自动 (默认行为)

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

已禁用

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 元素位于未设置 XYFocusKeyboardNavigation 的辅助 StackPanel (ContainerSecondary), 然后继承主容器 设置。 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 键导航(IsEnabledIsTabStop 属性为 true),并且逻辑选项卡顺序派生自应用程序中的控件布局。 但是,默认顺序不一定对应于视觉顺序。 实际显示位置可能取决于父布局容器以及可对子元素设置的某些属性来影响布局。

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

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

设置选项卡导航行为

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

注意

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

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

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

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

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

    “本地”选项卡导航行为

    “本地”选项卡导航行为

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

    “一次”选项卡导航行为

    “一次”选项卡导航行为

  • 周期
    焦点循环回到容器内的初始可聚焦元素。 对于此示例,制表位顺序为 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.TabFocusNavigationControl.TabNavigation 的工作方式相同。

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

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

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

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

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

当 B3(在方向区域中)获得焦点时,范围将更改,选项卡导航将转移到确定后续焦点的最佳候选位置的方向区域。 在这种情况下,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 应用程序的 UI 之间的导航和交互,非指针输入类型(例如键盘、手柄、遥控器)和辅助功能工具(如 Windows 讲述人)共享一种通用的基础机制。

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

有关构建适用于 Xbox/电视的应用和体验的更多一般信息,请参阅键盘交互针对 Xbox 和电视进行设计游戏板和遥控器交互

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

通过以下导航策略属性,你可以根据箭头键、方向垫(D-pad)按钮或类似按下来影响哪些控件接收焦点。

  • 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