WPF Accessibility for DevelopersWelcome to the WPF Accessibility for Developers course. This course is tailored to your role as a developer using WPF and UI Automation (UIA), and will give you specific tools and skills to create more accessible, user-friendly products. As a result of this course you will be able to:
WelcomeIt is assumed that you are already somewhat familiar with the Windows Presentation Foundation (WPF) framework. This course is organized to help you locate information quickly when you need it, and is laid out in the following manner:
Programmatic AccessWindows Presentation Foundation (WPF) uses the newest accessibility application programming interface (API), UI Automation (UIA), to programmatically expose information to users of assistive technology (AT). Developers use the WPF code, as well as its declarative markup language XAML, to create products with rich functionality. Making WPF applications and products accessible requires a clear understanding of UIA and how it works. UI Automation BasicsUIA is a new accessibility framework, that enables programmatic access to AT through six components:
Exploring UIA ComponentsUIA Tree The UIA Tree represents the entire UI. The root element is always the desktop, and the child elements (called fragments) are application windows. The UIA Tree maps to all the elements on the desktop. The UIA Tree is not a fixed structure and is seldom seen in its totality because it might contain thousands of elements. Parts of it are built as they are needed, and it can undergo changes as elements are added, moved, or removed. To help AT clients process UI information more effectively, the framework supports alternative views of the UIA Tree which may be used by some applications:
Examples of UI items that contribute to the logical structure of the UI, but are not interactive themselves, are item containers such as list view headers, toolbars, menus, and the status bar.
For example, the values in a drop-down combo box will appear in the content view because they represent the information being used by an end user. In the content view, a combo box and list box are both represented as a collection of UI items where one, or perhaps more than one, item can be selected. That one is always open and one can expand and collapse is irrelevant in the content view because it is designed to show the data, or content, that is being presented to the user. Automation Element UIA exposes every piece of the UI to client applications as an Automation Element. Providers suplly property values for each element. Elements are exposed as a tree structure, with the desktop as the root element. Automation elements sometimes implement methods that allow AT to interact with the element. When the user invokes a method on an element, it automatically invokes the corresponding method on the provider. Automation Element Properties UIA providers expose properties of UI Automation elements. These properties enable UIA client applications to discover information about pieces of the user interface (UI), especially controls, including both static and dynamic data. The UIA specification defines two kinds of automation properties:
Control Patterns Control patterns are collections of associated properties, events, and methods that describe an element. There are 22 control patterns defined to date, and more than one pattern can apply to a single element. Control patterns also expose methods that enable clients to get further information about the element and to provide input. The pattern might be something simple like the Invoke pattern, which lets clients invoke a control. Info icon: Pattern Interfaces Each pattern is its own interface. When you implement a pattern you must implement all the properties and entire interface correctly or it will not compile properly. Automation Events Automation events notify applications of changes to and actions taken wit automation elements. Unlike WinEvents, UIA events are not based on a broadcast mechanism. UIA clients register for specific event notifications and can request that specific UIA properties and control pattern information be passed into their event handlers. In addition, a UIA event contains a reference to the element that raised it. Providers can improve performance by raising events selectively, depending on whether any clients are listening. Pitfall: Test Before Implementing Events Test your code before implementing events. This allows for tweaks to the code before adding the additional complexity of events to it. Control Type Automation elements expose common properties of the UI elements they represent. One of these properties is the UIA control type, which defines its basic appearance and functionality as a single recognizable entity, for example, a button or check box. In UI Automation, a control type is a set of conditions that a control must meet in order to use the ControlTypeProperty property. The conditions include specific guidelines for UIA tree structure, UIA property values, control patterns, and UIA events. Select the correct control type for your control by thinking through the functionality of the control, not its appearance. For example, an image that is clickable might use the Button control type, which has an invoke functionality, whereas the Image control type does not. Info icon: Control Type Flexibility The total number of pre-defined control types is significantly lower than MSAA accRole definitions, because you can combine UIA control patterns to express a larger set of features while MSAA roles cannot. You can also customize the description of control type by LocalizedControlType property while keeping the baseline type as defined. Additional information about the Microsoft UI Automation Community Promise and UI Automation Specification can be found on MSDN. Required Properties: Name and Automation IDWhen writing the code there are two required properties that must be set directly by the developer in the markup rather than allowed to default in order be accessible. These are:
Name The Name for an automation element is assigned by the developer. The Name property should always be consistent with the label text on screen. For example, the Name must be “Browse…” for the button element with “Browse…” as the label. Do not include the control role or type information such as “button” or “list” in the Name. This will cause conflict with the text from LocalizedControlType property. The control type and control patterns are responsible for describing the functionality, not the Name, of an element. Alt Text When the corresponding label text is not visible on screen or when it is replaced by graphics, alternative text (alt text) should be chosen. Good alt text is a concise description of the UI function or the feature as if it was labeled by the simple text. For example, the Windows Start menu button should have the Name “Start” instead of “Windows Logo on blue round sphere graphics.” This is set in the code like this, for example: <ListBox AutomationProperties.Name = “States”/> Automation ID The Automation ID exclusively identifies a UIA Element within its siblings and the product. The Automation ID is ideally a unique identity for the entire logical hierarchy but is required to be unique within its siblings. For example, an application may contain a menu control with multiple top-level menu items that, in turn, have multiple child menu items. These secondary menu items may be identified by a generic scheme such as "Item1," "Item 2," and so on, allowing duplicate identifiers for children across top-level menu items; however, giving each item a completely unique name or string within the product allows for easier identification. The Automation ID is noted in the XAML side of the code. Below is an example of the code to add the Name and Automation ID looks like: Language and Automation ID: The Automation ID does not change regardless of language. A button name might be different in French, but the Automation ID retains the original name given by the developer. Common ControlsImplementing accessibility using WPF common controls is simple. If the common control is implemented correctly, then the only additional work required for accessibility is ensuring that the Name and Automation ID have been entered into the XAML code. The UIA provider will automatically expose the tree, automation elements, properties, methods and events to the client. Required StepsIn order to implement UIA in WPF, developers must start by completing the three steps described in the table below.
Keyboard NavigationYou must plan for effective keyboard interactions in your product. Think through the navigation for the product, which items should receive focus, and how non-text items will be labeled in order to provide the best experience for users. All applications must be navigable using only a keyboard. Tab order and shortcut keys should all be provided in the spec. The KeyboardNavigation class is responsible for implementing default keyboard focus navigation when one of the navigation keys is pressed. The navigation keys are: TAB, SHIFT+TAB, CTRL+TAB, CTRL+SHIFT+TAB, UPARROW, DOWNARROW, LEFTARROW, and RIGHTARROW keys. The navigation behavior of a navigation container can be changed by setting the attached KeyboardNavigation properties TabNavigation, ControlTabNavigation, and DirectionalNavigation. These properties are of type KeyboardNavigationMode and the possible values are Continue, Local, Contained, Cycle, Once, and None. The default value is Continue, which means the element is not a navigation container. The following example creates a Menu with a number of MenuItem objects. The TabNavigation attached property is set to Cycle on the Menu. When focus is changed using the tab key within the Menu, focus will move from each element and when the last element is reached focus will return to the first element. Menu navigationMenu = new Menu(); Further keyboard navigation information can be found on MSDN. High DPIWriting a dpi-aware application is the key to making a UI look consistently good across a wide variety of high dpi display settings. An application that is not dpi-aware but is running on a high dpi display setting can suffer from many visual artifacts, including incorrect scaling of UI elements, clipped text, and blurry images. By adding support in your application for dpi-awareness, you guarantee that the presentation of your application’s UI is more predictable, making it more visually appealing to users. As more manufacturers ship greater numbers of high-resolution displays, the default 96 DPI setting can no longer be assumed by applications. WPF is dpi-aware, but you must still avoid hardcoding layouts, size, or font, and instead use percentages where possible to ensure the scaling works for all users. High ContrastSupporting high contrast is an important part of making your application accessible. A high contrast mode must remove flashing animations, and remove or reduce transition animations. It should also be set to omit non-functional images, gradients or patterns behind text. When creating applications, be sure to adhere to the high contrast standards. Be sure not to hard-code colors and have alternative color icons available for high-contrast setting users. Other ConsiderationsThere are two additional accessibility considerations that you need to think through when developing your product. These are color and sound. Color Text and background contrasts should meet at least a 5:1 color contrast ratio. In addition, keep color choices appropriate for users with colorblindness. Be sure to provide visual cues in formats which rely solely on color to convey changes or information in the user interface (UI). Different icons or use of words will help colorblind users get the information they need. Sound Provide closed captioning or visual notifications for sounds used in applications. Users with hearing impairments or computers that do not have sound available will need alternate notifications such as these. Custom ControlsMost custom controls in WPF are actually derived from common controls. When implementing a custom control, you must override the "Core" methods from the base automation peer class that describe behavior unique and specific to your custom control. Before you can assign a control type to an element, the element needs to meet certain conditions, including a particular automation tree structure, property values, control patterns, and events; however, you are not limited to using only these sets of conditions. You can extend a control with additional patterns and properties. In order to create a customized control with UIA support, there are five main steps to follow:
Control Types and Control Patterns: There is not a one-to-one correspondence between control types and control patterns. A control pattern may be supported by multiple control types, and a control may support multiple control patterns, each of which exposes different aspects of its behavior. Some controls have conditional support for several control patterns. For example, the menu item control has conditional support for the Invoke, Expand Collapse, Toggle, and SelectionItem control patterns, depending on the menu item control’s function within the menu control. Know Before you GoWPF supports UIA through a tree of peer automation objects that parallels the tree of user interface elements. Test code and applications that provide accessibility features can use automation peer objects directly (for in-process code) or through the generalized interface provided by UIA. In order to understand the customization process in UIA, it is important to have a clear understanding of automation peers and their role in UIA. Some customized controls require custom automation peer implementation when a single automation peer-derived control is insufficient to convey functionality. This section introduces the following concepts about Automation Peers:
Automation Peer ClassesWPF controls support UI Automation through a tree of peer classes that derive from AutomationPeer. By convention, peer class names begin with the control class name and end with "AutomationPeer". For example, ButtonAutomationPeer is the peer class for the Button control class. The peer classes are roughly equivalent to UI Automation control types but are specific to WPF elements. Elements implement an automation peer class if they accept interface activity from the user, or if they contain information needed by users of AT. Not all WPF visual elements have automation peers. A few examples of classes that implement automation peers are Button, TextBox, and Label. Examples of classes that do not implement automation peers are classes that derive from Decorator, such as Border, and classes based on Panel, such as Grid and Canvas. Custom Peer Classes: The base Control class may not have a corresponding peer class. If you need a peer class to correspond to a custom control that derives from a control that does not have a corresponding peer class, you should derive the custom peer class from FrameworkElementAutomationPeer. For example, if you are deriving a custom control from Button and you require a custom automation peer that has the traits of a button automation peer, then you should derive from the button automation peer. If, however, you need a button that pulls out into a 3-D picture, for example, then the FrameworkElementAutomationPeer would be a better choice to derive from. Customizations in a Derived PeerIf you are creating a custom automation peer, you will need to override the default automation peer for your control. All classes that derive from UIElement and ContentElement contain the protected virtual method OnCreateAutomationPeer. WPF calls OnCreateAutomationPeer to get the automation peer object for each control. Automation code can use the peer to get information about a control’s characteristics and features and to simulate interactive use. A custom control that supports automation must override OnCreateAutomationPeer and return an instance of a class that derives from AutomationPeer. For example, if a custom control derives from the ButtonBase class, then the object returned by OnCreateAutomationPeer should derive from ButtonBaseAutomationPeer. Peer NavigationThe UIA tree for a custom peer should be correct by default. After locating an automation peer, in-process code can navigate the peer tree by calling the object's GetChildren and GetParent methods. Navigation among WPF elements within a control is supported by the peer's implementation of the GetChildrenCore method. The UI Automation system calls this method to build up a tree of subelements contained within a control; for example, list items in a list box. The default UIElementAutomationPeer.GetChildrenCore method traverses the visual tree of elements to build the tree of automation peers. Custom controls override this method to expose children elements to automation clients, returning the automation peers of elements that convey information or allow user interaction. When creating a customized control using UIA it is important to be aware of this navigation process. Expose the Control to UIAWhen UIA queries the root of a WPF application about an element, the root returns the AutomationPeer for that element. Standard WPF controls implement default properties for the AutomationPeer and are supplemented by developers with additional non-default properties. Custom controls require further work to ensure that programmatic access is correctly implemented. The AutomationPeer provides information about the element to AT such as the control type, properties, and patterns. It also raises events when the client registers to listen for them. If you create custom controls, you need a way to expose these control patterns and properties. Using automation peers to do this in WPF, you have two options:
Override the OnCreateAutomationPeer method for your custom control so that it returns your provider object, which must derive directly or indirectly from AutomationPeer. For example, if a custom control that is derived from Button is named MyButton, the object returned by OnCreateAutomationPeer should be MyButtonAutomationPeer. Security Considerations for Derived Peers: When overriding an automation peer you must use the correct assembly. Automation peers must run in a partial-trust environment. Code in the UIAutomationClient assembly is not configured to run in a partial-trust environment, and automation peer code should not reference that assembly. Instead, you should use the classes in the UIAutomationTypes assembly. For example, you should use the AutomationElementIdentifiers class from the UIAutomationTypes assembly, which corresponds to the AutomationElement class in the UIAutomationClient assembly. Reference the UIAutomationTypes assembly in automation peer code. Provide Property ValuesAutomation code gets information about your control by calling public methods of the peer class. To provide information about your control, override each method whose name ends with "Core" when your control implementation differs from that of that provided by the base automation peer class. At a minimum, your control must implement the GetClassNameCore and GetAutomationControlTypeCore methods, as shown in the following example: ControlType Values: Your implementation of GetAutomationControlTypeCore describes your control by returning a ControlType value. Although you can return ControlType.Custom, you should return one of the more specific control types if it accurately describes your control. A return value of ControlType.Custom requires extra work for the provider to implement UI Automation, and UI Automation client products are unable to anticipate the control structure, keyboard interaction, and possible control patterns. Filter the UI Automation TreeWhen elements appear in the UIA Tree that are not desired as a result of a custom automation peer, there are ways to filter information for your users. Implement the IsContentElementCore and IsControlElementCore methods to indicate whether your control contains data content or fulfills an interactive role in the user interface (or both). By default, both methods return True. These settings improve the usability of AT, which may use these methods to filter the automation tree. If your GetPattern method transfers pattern handling to a subelement peer, the subelement peer's IsControlElementCore method can return False to hide the subelement peer from the automation tree. For example, scrolling in a ListBox is handled by a ScrollViewer, and the automation peer for PatternInterface.Scroll is returned by the GetPattern method of the ScrollViewerAutomationPeer that is associated with the ListBoxAutomationPeer. Therefore, the IsControlElementCore method of the ScrollViewerAutomationPeer returns false, so that the ScrollViewerAutomationPeer does not appear in the automation tree. Automation Properties: Your automation peer should provide appropriate default values for your control. Note that XAML that references your control can override your peer implementations of core methods by including AutomationProperties attributes. For example, the following XAML creates a button that has two customized UI Automation properties. <Button AutomationProperties.Name="Special" AutomationProperties.HelpText="This is a special button."/> Enable the Client to Interact with the Control Using MethodsAutomation peers simplify some implementation aspects of server-side UI Automation providers, but custom control automation peers must still handle pattern interfaces. Like non-WPF providers, peers support control patterns by providing implementations of interfaces in the System.Windows.Automation.Provider namespace, such as IInvokeProvider. The control pattern interfaces can be implemented by the peer itself or by another object. The peer's implementation of GetPattern returns the object that supports the specified pattern. UI Automation code calls the GetPattern method and specifies a PatternInterface enumeration value. Your override of GetPattern should return the object that implements the specified pattern. If your control does not have a custom implementation of a pattern, you can call the base type's implementation of GetPattern to retrieve either its implementation or null if the pattern is not supported for this control type. For example, a custom NumericUpDown control can be set to a value within a range, so its UI Automation peer would implement the IRangeValueProvider interface. The following example shows how the peer's GetPattern method is overridden to respond to a PatternInterface.RangeValue value. A GetPattern method can also specify a subelement as a pattern provider. The following code shows how ItemsControl transfers scroll pattern handling to the peer of its internal ScrollViewer control. To specify a subelement for pattern handling, this code gets the subelement object, creates a peer by using the CreatePeerForElement method, sets the EventsSource property of the new peer to the current peer, and returns the new peer. Setting EventsSource on a subelement prevents the subelement from appearing in the automation peer tree and designates all events raised by the subelement as originating from the control specified in EventsSource. The ScrollViewer control does not appear in the automation tree, and scrolling events that it generates appear to originate from the ItemsControl object. Implement Pattern ProvidersThe interfaces implemented by a custom provider are explicitly declared if the owning element derives directly from Control. For example, the following code declares a peer for a Control that implements a range value. If the owning control derives from a specific type of control such as RangeBase, the peer can be derived from an equivalent derived peer class. In this case, the peer would derive from RangeBaseAutomationPeer, which supplies a base implementation of IRangeValueProvider. The following code shows the declaration of such a peer. Raise EventsAutomation clients can subscribe to automation events. Custom controls must report changes to control state by calling the RaiseAutomationEvent method. Similarly, when a property value changes, call the RaisePropertyChangedEvent method. The code below shows how to get the peer object from within the control code and call a method to raise an event. As an optimization, the code determines if there are any listeners for this event type. Raising the event only when there are listeners avoids unnecessary overhead and helps the control remain responsive. Raise Events from a UI Automation ProviderThe example code in this section demonstrates how a UI Automation event is raised in the implementation of a custom button control. The implementation enables a UI Automation client application to simulate a button click. To avoid unnecessary processing, the example checks ClientsAreListening to see whether events should be raised. Additional Custom Control GuidelinesDevelopers should remember the following points when they create a custom control with Automation support.
High contrast in WPF can be accomplished in some additional steps. You must examine which high contrast theme is on and set your custom control’s colors based on that. An example of this code would be: <Setter Property="Selector.IsSelected" Value="{DynamicResource {x:Static SystemParameters.HighContrastKey}}" /> Further information about high contrast in WPF can be found on the devnation blog. Tools & ResourcesNow that you’ve learned more about developing accessible products and services, you may want to explore the subject further. Here are some online resources you might find helpful.
Course CompletionCongratulations, you have completed the WPF Accessibility for Developers course. You now have an increased understanding on how to use Windows Presentation Foundation (WPF) common controls to create more accessible applications. You can also see the benefits of including accessibility into your product development cycle in a programmatic way to enhance the development process. Further courses are available on how to write accessible code and address platform-specific issues for accessibility. You can also consult the Microsoft Accessibility developer portal on MSDN for more information. The courses in the Accessibility Training for Developers include:
|