Поделиться через


Implementing keyboard accessibility (XAML)

[This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation]

Looking for the HTML/JavaScript version of this topic? See Implementing keyboard accessibility (HTML).

Many users who are blind or have mobility issues rely on the keyboard as the sole means of navigating your app UI and accessing its functionality. If your app does not provide good keyboard access, these users can have difficulty using your app or may not be able to use it at all.

See this feature in action as part of our App features, start to finish series: User interaction: Touch input... and beyond

Keyboard navigation among UI elements

To use the keyboard with a control, the control must have focus, and to receive focus (without using a pointer) the control must be accessible in a UI design via tab navigation. By default, the tab order of controls is the same as the order in which they are added to a design surface, listed in XAML, or programmatically added to a container.

In most cases, the default order based on how you defined controls in XAML is the best order, especially because that is the order in which the controls are read by screen readers. However, the default order does not necessarily correspond to the visual order. The actual display position might depend on the parent layout container and certain properties that you can set on the child elements to influence the layout. To be sure your app has a good tab order, test this behavior yourself. Especially if you have a grid metaphor or table metaphor for your layout, the order in which users might read versus the tab order could end up different. That's not always a problem in and of itself. But just make sure to test your app's functionality both as a touchable UI and as a keyboard-accessible UI and verify that your UI makes sense either way.

You can make the tab order match the visual order by adjusting the XAML. Or you can override the default tab order by setting the TabIndex property, as shown in the following example of a Grid layout that uses column-first tab navigation.

<!--Custom tab order.--> 
<Grid>
  <Grid.RowDefinitions>...</Grid.RowDefinitions>
  <Grid.ColumnDefinitions>...</Grid.ColumnDefinitions>

  <TextBlock Grid.Column="1" HorizontalAlignment="Center">Groom</TextBlock>
  <TextBlock Grid.Column="2" HorizontalAlignment="Center">Bride</TextBlock>

  <TextBlock Grid.Row="1">First name</TextBlock>
  <TextBox x:Name="GroomFirstName" Grid.Row="1" Grid.Column="1" TabIndex="1"/>
  <TextBox x:Name="BrideFirstName" Grid.Row="1" Grid.Column="2" TabIndex="3"/>

  <TextBlock Grid.Row="2">Last name</TextBlock>
  <TextBox x:Name="GroomLastName" Grid.Row="2" Grid.Column="1" TabIndex="2"/>
  <TextBox x:Name="BrideLastName" Grid.Row="2" Grid.Column="2" TabIndex="4"/>
</Grid>

You may want to exclude a control from the tab order. You typically do this only by making the control noninteractive, for example by setting its IsEnabled property to false. A disabled control is automatically excluded from the tab order. But occasionally you might want to exclude a control from the tab order even if it is not disabled. In this case, you can set the IsTabStop property to false.

Any elements that can have focus are usually in the tab order by default. The exception to this is that certain text-display types such as RichTextBlock can have focus so that they can be accessed by the clipboard for text selection; however, they're not in the tab order because it is not expected for static text elements to be in the tab order. They're not conventionally interactive (they can't be invoked, and don't require text input, but do support the Text control pattern that supports finding and adjusting selection points in text). Text should not have the connotation that setting focus to it will enable some action that's possible. Text elements will still be detected by assistive technologies, and read aloud in screen readers, but that relies on techniques other than finding those elements in the practical tab order.

Whether you adjust TabIndex values or use the default order, these rules apply:

  • UI elements with TabIndex equal to 0 are added to the tab order based on declaration order in XAML or child collections.
  • UI elements with TabIndex greater than 0 are added to the tab order based on the TabIndex value.
  • UI elements with TabIndex less than 0 are added to the tab order and appear before any zero value. This potentially differs from HTML's handling of its tabindex attribute (and negative tabindex was not supported in older HTML specifications).

Keyboard navigation within a UI element

For composite elements, it is important to ensure proper inner navigation among the contained elements. A composite element can manage its current active child to reduce the overhead of having all child elements able to have focus. Such a composite element is included in the tab order, and it handles keyboard navigation events itself. Many of the composite controls you use for a Windows Runtime app using C++, C#, or Visual Basic already have some inner navigation logic built into the into control's event handling. For example, arrow-key traversal of items is enabled by default on the ListView, GridView, ListBox and FlipView controls.

Keyboard alternatives to pointer actions and events for specific control elements

Ensure that UI elements that can be clicked can also be invoked by using the keyboard. To use the keyboard with a UI element, the element must have focus. Only classes that derive from Control support focus and tab navigation.

For UI elements that can be invoked, implement keyboard event handlers for the Spacebar and Enter keys. This makes the basic keyboard accessibility support complete and enables users to accomplish basic app scenarios by using only the keyboard; that is, users can reach all interactive UI elements and activate the default functionality.

In cases where an element that you want to use in the UI cannot have focus, you could create your own custom control. You must set the IsTabStop property to true to enable focus and you must provide a visual indication of the focused state by creating a visual state that decorates the UI with a focus indicator. However, it is often easier to use control composition so that the support for tab stops, focus, and Microsoft UI Automation peers and patterns are handled by the control within which you choose to compose your content.

For example, instead of handling a pointer-pressed event on an Image, you could wrap that element in a Button to get pointer, keyboard, and focus support.

<!--Don't do this.-->
<Image Source="sample.jpg" PointerPressed="Image_PointerPressed"/>

<!--Do this instead.-->
<Button Click="Button_Click"><Image Source="sample.jpg"/></Button>

Keyboard shortcuts

In addition to implementing keyboard navigation and activation for your app, it is a good practice to implement shortcuts for your app's functionality. Tab navigation provides a good, basic level of keyboard support, but with complex forms you may want to add support for shortcut keys as well. This can make your application more efficient to use, even for people who use both a keyboard and pointing devices.

A shortcut is a keyboard combination that enhances productivity by providing an efficient way for the user to access app functionality. There are two kinds of shortcut:

  • An access key is a shortcut to a piece of UI in your app. Access keys consist of the Alt key plus a letter key.
  • An accelerator key is a shortcut to an app command. Your app may or may not have UI that corresponds exactly to the command. Accelerator keys consist of the Ctrl key plus a letter key.

It is imperative that you provide an easy way for users who rely on screen readers and other assistive technology to discover your app's shortcut keys. Communicate shortcut keys by using tooltips, accessible names, accessible descriptions, or some other form of on-screen communication. At a minimum, shortcut keys should be well documented in your app's Help content.

You can document access keys through screen readers by setting the AutomationProperties.AccessKey attached property to a string that describes the shortcut key. There is also an AutomationProperties.AcceleratorKey attached property for documenting non-mnemonic shortcut keys, although screen readers generally treat both properties the same way. Try to document shortcut keys in multiple ways, using tooltips, automation properties, and written Help documentation.

The following example demonstrates how to document shortcut keys for media play, pause, and stop buttons.

<Grid KeyDown="Grid_KeyDown">

  <Grid.RowDefinitions>
    <RowDefinition Height="Auto" />
    <RowDefinition Height="Auto" />
  </Grid.RowDefinitions>

  <MediaElement x:Name="DemoMovie" Source="xbox.wmv" 
    Width="500" Height="500" Margin="20" HorizontalAlignment="Center" />

  <StackPanel Grid.Row="1" Margin="10"
    Orientation="Horizontal" HorizontalAlignment="Center">

    <Button x:Name="PlayButton" Click="MediaButton_Click"
      ToolTipService.ToolTip="Shortcut key: Ctrl+P"
      AutomationProperties.AcceleratorKey="Control P">
      <TextBlock>Play</TextBlock>
    </Button>

    <Button x:Name="PauseButton" Click="MediaButton_Click"
      ToolTipService.ToolTip="Shortcut key: Ctrl+A" 
      AutomationProperties.AcceleratorKey="Control A">
      <TextBlock>Pause</TextBlock>
    </Button>

    <Button x:Name="StopButton" Click="MediaButton_Click"
      ToolTipService.ToolTip="Shortcut key: Ctrl+S" 
      AutomationProperties.AcceleratorKey="Control S">
      <TextBlock>Stop</TextBlock>
    </Button>

  </StackPanel>

</Grid>

Important  Setting AutomationProperties.AcceleratorKey or AutomationProperties.AccessKey doesn't enable keyboard functionality. It only reports to the UI Automation framework what keys should be used, so that such information can be passed on to users via assistive technologies. The implementation for key handling still needs to be done in code, not XAML. You will still need to attach handlers for KeyDown or KeyUp events on the relevant control in order to actually implement the keyboard shortcut behavior in your app. Also, the underline text decoration for an access key is not provided automatically. You must explicitly underline the text for the specific key in your mnemonic as inline Underline formatting if you wish to show underlined text in the UI.

 

For simplicity, the preceding example omits the use of resources for strings such as "Ctrl+A". However, you must also consider shortcut keys during localization. Localizing shortcut keys is relevant because the choice of key to use as the shortcut key typically depends on the visible text label for the element. For more info, see Globalizing your app.

For more guidance about implementing shortcut keys, see Shortcut keys in the Windows User Experience Interaction Guidelines.

Implementing a key event handler

Input events such as the key events use an event concept called routed events. A routed event can bubble up through the child elements of a composited control, such that a common control parent can handle events for multiple child elements. This event model is convenient for defining shortcut key actions for a control that contains several composite parts that by design cannot have focus or be part of the tab order.

For example code that shows how to write a key event handler that includes checking for modifiers such as the Ctrl key, see Responding to keyboard input.

For more info about the routed-event concept in general, see Events and routed events overview.

Keyboard navigation for custom controls

We recommend the use of arrow keys as keyboard shortcuts for navigating among child elements, in cases where the child elements have a spacial relationship to each other. If tree-view nodes have separate subelements for handling expand–collapse and node activation, use the left and right arrow keys to provide keyboard expand–collapse functionality. If you have an oriented control that supports directional traversal within the control content, use the appropriate arrow keys.

Generally you implement custom key handling for custom controls by including an override of OnKeyDown and OnKeyUp methods as part of the class logic.

An example of a visual state for a focus indicator

We mentioned earlier that any custom control that enables the user to focus it should have a visual focus indicator. Usually that focus indicator is as simple as drawing a rectangle shape immediately around the control's normal bounding rectangle. The Rectangle for visual focus is a peer element to the rest of the control's composition in a control template, but is initially set with a Visibility value of Collapsed because the control isn't focused yet. Then, when the control does get focus, a visual state is invoked that specifically sets the Visibility of the focus visual to Visible. Once focus is moved elsewhere, another visual state is called, and the Visibility becomes Collapsed.

All of the default XAML controls for the Windows Runtime will display an appropriate visual focus indicator when focused (if they can be focused). There are also potentially different looks depending on the user's selected theme (particularly if the user is using a high contrast mode.) If you're using the XAML controls in your UI and not replacing the control templates, you don't need to do anything extra to get visual focus indicators on controls that behave and display correctly. But if you're intending to retemplate a control, or if you're curious about how XAML controls provide their visual focus indicators, the remainder of this section explains how this is done in XAML and in the control logic.

Here's some example XAML that comes from the Windows Runtime default XAML template for a Button.

<ControlTemplate TargetType="Button">
... 
    <Rectangle 
      x:Name="FocusVisualWhite" 
      IsHitTestVisible="False" 
      Stroke="{ThemeResource FocusVisualWhiteStrokeThemeBrush}" 
      StrokeEndLineCap="Square" 
      StrokeDashArray="1,1" 
      Opacity="0" 
      StrokeDashOffset="1.5"/>
    <Rectangle 
      x:Name="FocusVisualBlack" 
      IsHitTestVisible="False" 
      Stroke="{ThemeResource FocusVisualBlackStrokeThemeBrush}" 
      StrokeEndLineCap="Square" 
      StrokeDashArray="1,1" 
      Opacity="0" 
      StrokeDashOffset="0.5"/>
...
</ControlTemplate>

So far this is just the composition. To control the focus indicator's visibility, you define visual states that toggle the Visibility property. This is done using the VisualStateManager.VisualStateGroups attached property, as applied to the root element that defines the composition.

<ControlTemplate TargetType="Button">
  <Grid>
    <VisualStateManager.VisualStateGroups> 
       <!--other visual state groups here-->
       <VisualStateGroup x:Name="FocusStates"> 
         <VisualState x:Name="Focused"> 
           <Storyboard> 
             <DoubleAnimation 
               Storyboard.TargetName="FocusVisualWhite" 
               Storyboard.TargetProperty="Opacity" 
               To="1" Duration="0"/>
             <DoubleAnimation 
               Storyboard.TargetName="FocusVisualBlack" 
               Storyboard.TargetProperty="Opacity" 
               To="1" Duration="0"/>
         </VisualState> 
         <VisualState x:Name="Unfocused" /> 
         <VisualState x:Name="PointerFocused" /> 
       </VisualStateGroup>
     <VisualStateManager.VisualStateGroups>
<!--composition is here-->
   </Grid>
</ControlTemplate>

Note how only one of the named states adjusts Visibility directly whereas the others are seemingly empty. The way that visual states work is that as soon as the control uses another state from the same VisualStateGroup, any animations applied by the previous state are immediately canceled. Because the default Visibility from composition is Collapsed, this means the rectangle will not appear. The control logic controls this by listening for focus events like GotFocus and changing the states with GoToState. Often this is already handled for you if you are using a default control or customizing based on a control that already has that behavior. For more info on how to use visual states, see Storyboarded animations for visual states.

Keyboard accessibility and Windows Phone

A Windows Phone device typically doesn't have a dedicated, hardware keyboard. However, a Soft Input Panel (SIP) can support several keyboard accessibility scenarios. Screen readers can read text input from the Text SIP, including announcing deletions. Users can discover where their fingers are because the screen reader can detect that the user is scanning keys, and it reads the scanned key name aloud. Also, some of the keyboard-oriented accessibility concepts can be mapped to related assistive technology behaviors that don't use a keyboard at all. For example, even though a SIP won't include a Tab key, Narrator supports a touch gesture that's the equivalent of pressing the Tab key, so having a useful tab order through the controls in a UI is still an important accessibility principle. Arrow keys as used for navigating the parts within complex controls are also supported through Narrator touch gestures. Once focus has reached a control that's not for text input, Narrator supports a gesture that invokes that control's action.

Keyboard shortcuts aren't typically relevant for Windows Phone apps, because a SIP won't include Control or Alt keys.

Responding to keyboard input

Input: Touch keyboard sample

Responding to the appearance of the on-screen keyboard sample

XAML accessibility sample

Storyboarded animations for visual states