使用焦点导航为精通键盘的用户、残障人士及其他有辅助功能需求的人士、电视屏幕上的 10 英尺体验和 Xbox One 提供全面且一致的交互体验,并设计自定义控件。
概述
焦点导航是指使用户能够使用键盘、游戏板或远程控件导航和与 Windows 应用程序的 UI 进行交互的基础机制。
注释
输入设备通常归类为指向设备,例如触摸、触摸板、笔和鼠标,以及非指向设备,例如键盘、游戏板和远程控制。
本主题介绍如何优化 Windows 应用程序,并为依赖非点输入类型的用户生成自定义交互体验。
尽管我们专注于电脑上 Windows 应用中自定义控件的键盘输入,但设计良好的键盘体验对于软件键盘(如触摸键盘和屏幕键盘(OSK))也很重要,支持 Windows 讲述人等辅助工具,并支持 10 英尺体验。
请参阅 处理指针输入,了解如何在 Windows 应用程序中生成用于指向设备的自定义体验。
有关生成键盘应用和体验的更常规信息,请参阅 键盘交互。
通用指南
只有需要用户交互的 UI 元素应支持焦点导航,不需要操作的元素(如静态图像)不需要键盘焦点。 屏幕阅读器和类似的辅助功能工具仍会报出这些静态元素,即使它们不包含在焦点导航中也是如此。
请务必记住,与使用指针设备(如鼠标或触摸)导航不同,焦点导航是线性的。 实现焦点导航时,请考虑用户如何与应用程序交互以及逻辑导航应是什么。 在大多数情况下,我们建议自定义焦点导航行为遵循用户文化的首选阅读模式。
其他一些重点导航注意事项包括:
- 控件是否按逻辑分组?
- 是否有具有更大重要性的控件组?
- 如果是,这些组是否包含子组?
- 布局是否需要自定义方向导航(使用箭头键)和 Tab 键顺序?
无障碍工程软件 电子书中有一章关于 逻辑层次设计的内容非常出色。
键盘的 2D 方向导航
控件或控件组的 2D 内部导航区域称为其“方向区域”。 当焦点移动到此对象时,键盘箭头键(向左、向右、向上和向下)可用于在方向区域中的子元素之间导航。
2D 内部导航区域或方向区域
可以使用 XYFocusKeyboardNavigation 属性(其可能的值为 自动、启用或 禁用)来管理通过键盘箭头键进行的二维内部导航。
注释
Tab 顺序不受此属性影响。 为了避免导航体验的混乱,我们建议在应用程序的选项卡导航顺序中显式指定方向区域 的子元素,而不要指定。 有关元素的制表符行为的更多信息,请参阅 UIElement.TabFocusNavigation 和 TabIndex 属性。
自动(默认行为)
设置为“自动”时,方向导航行为由元素的祖先或继承层次结构决定。 如果所有上级处于默认模式(设置为“自动”),则不支持使用键盘进行方向导航。
已禁用
将 XYFocusKeyboardNavigation 设置为 禁用,以禁用指向控件及其子元素的方向导航。
XYFocusKeyboardNavigation 禁用行为
在此示例中,主 StackPanel(ContainerPrimary)的 XYFocusKeyboardNavigation 设置为 启用。 所有子元素都继承此设置,可以使用箭头键导航到该设置。 但是,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,以支持 2D 方向导航到某个控件及其每个 UIElement 子对象。
设置后,使用箭头键的导航仅限于方向区域中的元素。 选项卡导航不受影响,因为所有控件仍可通过其 Tab 键顺序层次结构进行访问。
启用了 XYFocusKeyboardNavigation 的行为
在此示例中,主 StackPanel(ContainerPrimary)的 XYFocusKeyboardNavigation 设置为 启用。 所有子元素都继承此设置,可以使用箭头键导航到该设置。 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 和嵌套行为
下面是三个嵌套方向区域的更复杂的示例,其中:
- 当 B1 具有焦点时,只能导航到 B5(反之亦然),因为存在一个方向区域边界,其中 XYFocusKeyboardNavigation 设置为“已禁用”,从而使 B2、B3 和 B4 无法使用箭头键访问
- 当 B2 具有焦点时,只能导航到 B3(反之亦然),因为方向区域边界阻止箭头键导航到 B1、B4 和 B5
- 当 B4 具有焦点时,必须使用 Tab 键在控件之间导航
启用了 XYFocusKeyboardNavigation 和复杂的嵌套行为
选项卡导航
虽然箭头键可用于控件或控件组的 2D 方向导航,但 Tab 键可用于在 Windows 应用程序中的所有控件之间导航。
默认情况下,所有交互式控件都支持 Tab 键导航(IsEnabled 和 IsTabStop 属性 true),逻辑选项卡顺序派生自应用程序中的控件布局。 但是,默认顺序不一定对应于视觉顺序。 实际显示位置可能取决于父布局容器,同时,您可以通过对子元素设置某些属性来影响布局。
避免使用自定义选项卡顺序,使焦点在应用程序中四处跳转。 例如,窗体中的控件列表应具有从上到下和从左到右(具体取决于区域设置)的 Tab 键顺序。
在本部分中,我们将介绍如何完全自定义此选项卡顺序以适应你的应用。
设置选项卡导航行为
UIElement 的 TabFocusNavigation 属性指定其整个对象树(或方向区域)的选项卡导航行为。
注释
对于不使用 ControlTemplate 定义其外观的对象,请使用此属性而不是 Control.TabNavigation 属性。
如上一部分所述,为避免导航体验混乱,建议在应用程序的选项卡导航顺序中显式指定方向区域 的子元素,而不是。 有关元素的标签导航行为的详细信息,请参阅 UIElement.TabFocusNavigation 和 TabIndex 属性。
对于低于 Windows 10 创作者更新(内部版本 10.0.15063)的版本,选项卡设置仅限于 ControlTemplate 对象。 有关详细信息,请参阅 Control.TabNavigation。
TabFocusNavigation 的值是 KeyboardNavigationMode 类型,具有以下可能的值(请注意,这些示例不是自定义控件组,并且不需要使用箭头键进行内部导航):
本地(默认)选项卡索引在容器内的本地子树上识别。 对于此示例,Tab 顺序为 B1、B2、B3、B4、B5、B6、B7、B1。
“本地”选项卡导航行为
容器和所有子元素接收焦点一次。 对于此示例,选项卡顺序为 B1、B2、B7、B1(还演示了带有箭头键的内部导航)。
“一次”选项卡导航行为
周期
焦点循环回到容器内的初始可聚焦元素。 对于此示例,Tab 键顺序为 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,则会根据范围为其分配比可视化树中所有交互式控件的当前最高索引值(和最低优先级)更高的索引值。
控件的所有子元素都被视为范围,如果其中一个元素也具有子元素,则它们被视为另一个范围。 通过选择范围可视化树上的第一个元素来解决任何歧义。
若要从制表符顺序中排除控件,请将 IsTabStop 属性设置为 false。
通过设置 TabIndex 属性来重写默认的制表符顺序。
注释
TabIndex 的工作方式与 UIElement.TabFocusNavigation 和 Control.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 讲述人)共享一种通用的基础机制,用于导航和与 Windows 应用程序的 UI 交互。
在本部分中,我们将介绍如何通过一组支持基于焦点的非指针输入类型的导航策略属性来指定首选导航策略并微调应用程序中的焦点导航。
有关构建 Xbox/TV 应用和体验的更常规信息,请参阅 键盘交互、设计 Xbox 和电视,以及 游戏板和远程控制交互。
导航策略
导航策略适用于键盘、游戏板、远程控制和各种辅助功能工具。
通过以下导航策略属性,您可以通过箭头键、方向键(D-pad)按钮或类似的按键来影响哪个控件接收焦点。
- XY聚焦向上导航策略
- XYFocusDownNavigationStrategy
- XY聚焦左导航策略
- XYFocus右导航策略
这些属性的可能值为 自动(默认值)、NavigationDirectionDistance、投影或 RectilinearDistance 。
如果设置为 自动,则该元素的行为取决于其祖先元素。 如果将所有元素都设置为 自动,那么将使用 投影。
注释
其他因素(如以前聚焦的元素或与导航方向轴的邻近度)可能会影响结果。
投影
投影策略将焦点移到当前聚焦元素边缘 在导航方向投影 时遇到的第一个元素。
在此示例中,每个焦点导航方向都设置为投影。 请注意焦点如何从 B1 移动到 B4,绕过 B3。 这是因为 B3 不在投影区域中。 还要注意,从 B1 向左移动时,没有识别出焦点候选项。 这是因为 B2 相对于 B1 的位置消除了 B3 作为候选项。 如果 B3 与 B2 位于同一行中,则它是左侧导航的可行候选项。 B2 是一个可行的候选项,因为它与导航方向轴的邻近度不受干扰。
投影导航策略
导航方向距离
NavigationDirectionDistance 策略将焦点移动到最接近导航方向轴的元素。
与导航方向对应的边界矩形的边缘 扩展,并 投影 来标识候选目标。 遇到的第一个元素被我们标识为目标。 在多个候选项的情况下,最接近的元素被识别为目标。 如果仍有多个候选项,则最顶层/最左边的元素被标识为候选项。
NavigationDirectionDistance 导航方向距离策略
直线距离
RectilinearDistance 策略根据 2D 直线距离(Taxicab 几何图形)将焦点移动到最接近的元素。
用于确定最佳候选项的是每一个潜在候选项的主要距离和次要距离的总和。 在平局中,如果请求的方向是向上或向下,则选择左侧的第一个元素,如果请求的方向为左或向右,则选择顶部的第一个元素。
直线距离导航策略
此图显示,当 B1 获得焦点并请求向下移动时,B3 是 RectilinearDistance 的焦点候选项。 这建立在以下示例的计算基础上:
- 距离(B1,B3,向下)为 10 + 0 = 10
- 距离 (B1, B2, 向下) 为 0 + 40 = 30
- 距离 (B1, D, 向下) 为 30 + 0 = 30