How to Code WPF Applications for Accessibility
Guidance and code examples to help you make your Windows Presentation Foundation apps fully accessible
June 14, 2010
In this article, I want to walk you through the process of making a Windows Presentation Foundation (WPF) application fully accessible to users with disabilities. I intend to do so by example; that is, I'll focus on the actual steps and the associated code snippets first and foremost. That way, you can always refer back to this article when you develop WPF-accessible applications. The prescriptive guidance that follows will focus on using Microsoft User Interface Automation (UIA) as a tool to meeting accessibility mandates.
I'll assume that you've had the required accessibility 101 class and that you are up to speed on the terminology. If you haven't, it's a good time now to do some reading before continuing. See, for example, my article "Assess Your Access." So where do you begin?
Start with your user interface (UI) controls. If your application does not expose a UI, you don't need to be concerned with accessibility. If your application has a UI, then you need to focus on accessibility. For that, you'll need to make sure that any controls that make up your UI components are accessible. I'll explain how to do this by walking you through a set of design scenarios; that way, you can pick whatever design scenario applies to you.
WPF Design Scenarios
Here are the four WPF design scenarios I'll discuss:
• Scenario 1: WPF application with native controls
• Scenario 2: WPF application with custom controls where the custom controls are based on native controls
• Scenario 3: WPF application with custom controls where the custom controls are not based on native controls
• Scenario 4: WPF application with legacy controls
Note that the term native control refers to the set of controls that ship with WPF and are supported by Microsoft. For instance, a WPF Button or a WPF Label control is considered a native control because it ships with the framework and is supported by Microsoft. The term legacy control refers to the set of non-WPF controls that may be embedded in a WindowsHost control in a WPF application.
Also note that the scenarios using native controls are based on Microsoft UI Automation. Per MSDN, "Microsoft UI Automation is the new accessibility model for Microsoft Windows and is intended to address the needs of assistive technology products and automated testing tools". This is the main reason why I used UI Automation as an accessibility implementation tool. If your controls follow the UI Automation specification, your controls and your application will be accessible.
Let's create a test project that we can use to work with these scenarios. Create a WPF application based on a WPF Application template, as Figure 1 shows.
Figure 1: Creating a test project for WPF accessibility scenarios
Accept the default name. In the design view, add a button to the form by dragging a WPF button control to the form or entering the XAML fragment in the XAML file, as Figure 2 shows.
Figure 2: XAML button fragment
Margin="48,50,0,0" Name="Button1" VerticalAlignment="Top" Width="75" />
Let's pretend this simple application is our world-class application. It won't win any awards, but you will come to appreciate why I've kept it simple.
Solution to WPF Design Scenario 1
To work with the first WPF accessibility design scenario, you'll need to start by setting accessibility properties. Here are the steps to do so:
1. Identify the control on the form. It is a native WPF button control.
2. Go to this link: msdn.microsoft.com/en-us/library/ms743581.aspx. This link opens to a page that contains information about support for control types in UIA. Each link on that page shows the control specification for all the supported native controls in WPF.
3. Review the control specification for the type of control on the form. In this case, it is a button control, so click the second link, UI Automation Support for the Button Control Type. (The equivalent link is here: msdn.microsoft.com/en-us/library/ms742153.aspx.)
4. On the Button Control Type page, review and implement each section for the button control that is missing. Determine the set of properties that need to be implemented by comparing the button control's available properties in the example against the properties in the Required UI Automation Properties table. Notice that there are 12 properties for the button control. That doesn't mean that I have to implement all 12; most are implemented for me because the button is a native control. The properties that are not implemented are typically those where Visual Studio has no way to guess your intention.
Figure 3 shows that the simple test application contains three empty fields. Two of these fields (AcceleratorKeyProperty and HelpText) are required fields as per step 3.
I'll show you how to fill in one of these required properties. You can then use it as a pattern to tackle the remaining 10 properties for the button control on the form if you need to provide meaningful values for these fields. Figure 4 shows one property copied here for your benefit.
Figure 4: Accessibility property for the button control
According to Figure 4, AcceleratorKeyProperty property is required. The AcceleratorKeyProperty refers to a shortcut key that can be used to invoke the control's pattern. For this example, I'll choose to add the letter B as my accelerator. When the user presses Alt+B, the button click event will fire.
To set this accelerator, open the XAML page and find the button control. Enter the following in the XAML button fragment:
Notice the underscore prefixed to the first letter of the content value, Button. When you run the application, pressing the Alt key causes the first letter of the button content to be underlined. The Alt+B key combination invokes the click event. With that in mind, you are now able to tackle the remaining empty properties. You can also reassign appropriate values to the existing properties as well. Appropriate values help screen readers provide more meaningful information to impaired users.Setting accessibility patterns. After you've set the accessibility properties, your next task is to set accessibility patterns. To do so, go to msdn.microsoft.com/en-us/library/ms742153.aspx. Find the Required UI Automation Control Patterns section. Determine whether or not your button supports either the IInvokeProvider or IToggleProvider pattern. In our example, the button supports IInvokeProvider but not IToggleProvider. The specification confirms that this is acceptable, so there's nothing more to do here. As a side note, WPF allows the developer to do many creative things with any control. So if you turn a button into a tree control, for instance, that control is no longer a button control anymore and it cannot represent itself as such to an accessible client such as screen reader. The UI Automation specification essentially dictates that a button must behave like a button by following the properties, patterns, and events of a button. Setting accessibility events. To set accessibility events, go to msdn.microsoft.com/en-us/library/ms742153.aspx. Find the Required UI Automation Events section. Review the eight events for the button control. Ensure that your button supports all eight events. This doesn't mean that you have to implement all eight events because most are implemented for you. Make sure to read the notes section, which tells you which events are optional and which are required. If you need to implement these events, you'll need to use AddHandler syntax. The process of wiring events is fully described at msdn.microsoft.com/en-us/library/ms748252.aspx. The link lists the types of events that are supported for the applications that you write (called Automation Providers) and the adaptive technology applications such as screen readers (called Automation Clients). In the example I provided, although InvokeEvent is supported, it is not wired to code. Technically speaking, this is OK. However, a button that does nothing serves no useful purpose. So, I'll wire the click event to a handler containing code that pops a message box when the button is clicked. I'll leave this task as an exercise to the reader. That's it! If you take a look at the work done here, you've implemented accessibility for a single button for scenario 1. This is the reason why I kept the example intentionally simple. You'll need to repeat those steps for each control on the form. Solution to WPF Design Scenario 2 If you have used only native controls in your custom controls, you need to review scenario 1. This scenario covers all the native controls. In addition, you will need to implement code that describes your custom control to the adaptive technology client. For instance, if you have created a user control that faxes tax information to the IRS, that control needs to represent itself and its behavior accurately to an accessible client. Let's review a basic example. For this example, our custom control is based on a user control that inherits from UserControl base class. These implementation details are already set up for you when you create a project based on a WPF User Control library in Visual Studio, as shown in Figure 3. When an adaptive technology client, such as a screen reader, encounters your user control, the adaptive technology client will interrogate your control to discover what kind of control it is, what behavior(s) it supports, and what type of functionality is available. That information is then relayed to the impaired user. To make this user control accessible, I'll need to perform a couple of steps. First, inherit from the FrameworkElementAutomationPeer class found in the System.Windows.Automation.Peers namespace. That namespace is defined in the PresentationFramework assembly. The FrameworkElementAutomationPeer class represents every UI element on the desktop. That class contains five base methods (shown in Figure 5) that may seem mildly familiar to you; you've seen them in the control type specification.Figure 5: FrameworkElementAutomationPeer class signature public FrameworkElementAutomationPeer(FrameworkElement owner); protected override string GetAutomationIdCore(); protected override string GetHelpTextCore(); protected override AutomationPeer GetLabeledByCore(); protected override string GetNameCore();As you can see, these signatures end in the term core, so they are usually referred to as the core methods. When I implement these methods in my user control, the information will be displayed at runtime for accessibility clients, similar to the list or properties shown in Figure 1. As an example, GetAutomationIdCore returns a string that represents the AutomationId for your user control, and so on and so forth. The GetNameCore method is especially significant because it actually names your user control to the adaptive technology. You'll want to use an appropriate name in this case. In my case, since this is a tax button control, I'll name this IRSFax. Figure 6 shows an implementation of my user control. I am interested only in exposing information for these particular methods for my control.Figure 6: Implementing core methods protected override string GetAutomationIdCore() \{ return "IRSID"; \} protected override string GetNameCore() \{ return "IRSFAXControl"; \}Second, I'll need to create an instance of my user control that returns an AutomationPeer type. When the adaptive technology polls the user control for information, the action will invoke the OnCreateAutomationPeer method automatically, as Figure 7 shows. Figure 7: OnCreateAutomationPeer signature and implementation protected override AutomationPeer OnCreateAutomationPeer() \{ return new MyAutomationInfoClass(this); \}As the code shows, this will force the creation of MyAutomationInfoClass, which contains the code in Figure 6. If you do not implement these core methods, accessibility clients will not be able to provide meaningful information to disabled users. If you've been careful to use only native controls in your user control, that's all you'll have to implement to fulfill the accessibility mandate. Since we've done a lot here, let's recap. The user control that I created overrides methods found in the FrameworkElementAutomationPeer. I tie the two together by returning an instance of my user control type as an AutomationPeer object. The adaptive technology client will take over from this point. Solution to WPF Design Scenario 3 If you've developed a custom control based on controls that are not found on the UI Automation specification link (msdn.microsoft.com/en-us/library/ms743581.aspx), you'll need to go to the time-out corner because you have been very naughty! There's no easy way to handle this scenario. What you do next depends on whether or not you have the source code for the controls that you used. If you have the source code, then review the code to determine whether native controls were augmented to create a new control. If native controls were used, you'll need to decompose the controls into their constituent native controls, then use scenario 1 for native controls and scenario 2 for custom controls. As an example, consider the case where you have a custom control that uses a non-native DropDown control and a non-native List control to create a new custom control that behaves like a ListBox control. You can decompose the widget into a List and a DropDownList control. Then, you may look up the specifications for each decomposed control. Since the decomposed controls are not native controls, the information won't be totally accurate. If you are unable to decompose the control, you have only one option. Find a native control that very nearly matches your custom control in form and function and use its specification. In the example I mentioned previously, the new ListBox control may match the List control type. So, you can use that as a substitute. You cannot create your own specifications. This is why I sent you to the time-out corner; that process is cost-prohibitive. If you do not have the source code for the controls and you're required to implement accessibility, you'll need to somehow inject accessibility into the controls at runtime. It is possible, but it is akin to pulling teeth without an anesthetic. So, I'll reserve further discussion for a future article when I dive into the internals of UIA. Solution to WPF Design Scenario 4 WPF applications support hosting legacy Windows Forms controls via the WindowsFormsHost control. Accessibility in this scenario is not trivial because there are many moving parts. For instance, a WPF application may host a Windows forms control where the Windows forms control hosts a WPF control. The good news is that accessibility in interoperability scenarios (including hybrid interoperability, as is the case in the example just given) is fully supported. This means that your WPF application with legacy controls should work as designed with assistive technology. Scenario 4 is especially common if a company has invested a significant amount of time and resources in proprietary controls. Most times, and for a variety of reasons, it is not possible to simply migrate the code to WPF. In that case, you should test to see whether the information being returned is sufficient for impaired users. Pay attention to tabbing, focus, keyboard navigation, shortcut keys, and events. There are known issues with these in interoperability scenarios. These issues are compounded when accessibility is added into the mix. As an example, tabbing from a WPF control to a hosted control containing nested controls may result in behavior that is unexpected from the user's perspective. See msdn.microsoft.com/en-us/library/ms751797(VS.85).aspx for a detailed explanation. After testing your interoperability scenario, if you discover that the application is not behaving as expected, you will need to implement the IAccessible interface in your Windows forms control so that you can manually supplement the MSAA to UIA mapping. There’s more information here: msdn.microsoft.com/en-us/library/ms971352.aspx. However, you should note that this is not trivial to get right! Accessibility System Events Now that you've implemented accessibility for the native control on the form, you must make sure that your application complies with accessibility system settings. Typically, these system settings include High Contrast Mode support. This is where WPF shines brightly. WPF allows you to hook into High Contrast Mode so that when the high-contrast flag is set at the Operating System level, your application can respond to it. The Operating System flag is set whenever a user enters High Contrast mode by using the key combination Shift+Ctrl+Prnt Scrn or by selecting the High Contrast mode from control panel. Go ahead and give it a try!Review the code in Figure 8. The code hooks into the High Contrast mode using a style containing a trigger. When the trigger is engaged for High Contrast mode, a monochrome image is displayed. When the trigger is disengaged, a color image is displayed. Figure 8: Hooking the High Contrast event using triggers and styles As the code snippet shows, you can implement that functionality entirely in the XAML file. I recommend that you use the XAML approach because High Contrast mode changes the style of the controls. It doesn't modify the behavior. If it did, it would be a candidate for placing the functionality in the code. If you have avoided hard-coding in your application and you've used only native controls, High Contrast mode will automatically make the adjustments to your application. As a side note, most developers think of hard-coding only in terms of text and font sizes. However, Images that are loaded into a control and declared like this: are also hard-coded. When the button is instantiated by the runtime, its content is hard-wired to source the image from the hard-coded file path. In this case, any High Contrast changes made to the image will be overwritten. The correct way to implement accessibility requirements for these types of controls is shown in Figure 8; that is, create the image control with no image initially given by the first line in the XAML fragment. Then, load the image in a trigger that hooks into the HighContrastKey event. You'll need one image for the default style, ColoredImage.jpg, and one more for the High Contrast style, MonochromeImage.jpg. You can apply this idea to any control that needs to respond to system changes. The Truth About Accessibility Now that you have a clear picture of how to build accessibility into your WPF applications, I want to clarify some of the myths and truths about WPF and accessibility flying about on the Internet. • The use of native controls is preferred. This is true. Native controls do most of the accessibility work for you. You get this functionality for free. • WPF is fully accessible. This is not quite true. As I've shown you, the framework makes significant gains on accessibility, more than any other technology by far. However, you still need to do some work to bring your application up to full compliance. • Fully accessible WPF applications are easy to build. Again, this is not quite true. Accessibility is a two-way street. If you fully implement accessibility in your WPF application, the adaptive technology vendors still need to update their client applications to support UIA. Without UIA support, as explained before, the conversation between your application and the adaptive technology client is severely restricted. I'll discuss this in more depth in an upcoming article. • For accessible applications, avoid developing custom controls for WPF. This is true. As I've shown you, custom controls that are not based on native controls are extremely expensive with regard to accessibility. Avoid them whenever possible. Otherwise, you'll likely be very familiar with the time-out corner. You've reviewed some key strategies for making your WPF applications accessible and played with some simple examples, and now you have a basis for retrofitting your own applications to make them accessible. Let me know how it goes! Alvin Bruney is a longtime ASP.NET MVP and author of five books. His current book, ASP.NET 4 by Example, is currently available for $20 on www.lulu.com/owc.
Read more about:
MicrosoftAbout the Author
You May Also Like