While custom controls are introduced every day, not all of them are easily accessible.
This article provides a quick summary of Microsoft® technologies that help make Win32-based custom controls programmatically accessible. Techniques range from implementing UI Automation, to creating or overriding properties with Dynamic Annotation, to using the new IAccessibleEx interface to close the gap between UI Automation and Microsoft Active Accessibility®.
The Windows® operating system offers several technologies to help make controls accessible: Microsoft Active Accessibility, UI frameworks, and UI Automation. As Table 1 shows, the most effective technology depends on how developers introduce their new controls.
Before investing time designing a new control or customizing a standard control, you should ask yourself: “Do I really need that control?”
For controls and frameworks written from scratch, UI Automation is recommended today. While Microsoft Active Accessibility is handy for relatively simple controls, the technology doesn’t support the complexity of modern user interfaces (UI).
For controls based on common controls (such as COMCTL32.DLL or USER32.DLL), Dynamic Annotation enables modest customization. However, it may not be sufficient for significant customization or for sub-classed controls. For controls based on solid Microsoft Active Accessibility implementations, developers can use the IAccessibleEx Interface specification to enhance accessibility with new UI Automation interfaces.
This article introduces the basics of using UI Automation, Dynamic Annotation, and the IAccessibleEx interface for developing controls.
Quick Tips: “Do I Really Need that Control?”
Before investing time in designing a new control or customizing a standard control, you should ask yourself: “Do I really need that control?”
If you’re lucky, you may find a standard control that does the job. Many modern UI libraries support accessibility and customization.
If you customize a standard control, try not to overload it with features. Unnecessary complexity can result in expensive accessibility support cost, not to mention reduced usability. Never underestimate the implication of a small modification. If you have to introduce major customizations, consider creating a control from scratch
If you have to introduce a new control, try to follow standard practices to support UI accessibility. For example, never assume that all users have access to pointing devices, and try to use system or desktop theme colors to avoid accessibility problems with custom colors. While the focus of this article is on programmatic access to the user interface, these fundamental accessibility criteria are very important.
Many accessibility practices are available in the form of industry standards (such as Section 508 of the Federal Rehabilitation Act). Some useful resources are introduced at the Microsoft Accessibility Developer Center (http://msdn.microsoft.com/en-us/accessibility/default.aspx).
Part I: Implementing UI Automation
This article talks about implementing UI Automation for new controls in a few simplified steps. Designing a UI Automation object model for a new user interface is straightforward if you follow a few basic principles:
- Define user scenarios clearly. Actual user scenarios are helpful. During the design process, you shouldn’t rely on a single operation device such as mice, custom color, or screen size.
- Define the UI functionality as simply as possible. Is it clickable (such as a button or hyperlink)? Does it allow for selection (such as a list box)?
- Isolate fundamental UI elements by function or feature. Structure a complex UI in a way consistent with user scenarios. A keyboard navigation model is a useful guide.
- Match a UI feature from the previous step with existing controls. The UI Automation Control Type specification is a useful guideline. If you have trouble finding the correct control type, see the Control Pattern specifications and locate a control type that has the control patterns you want.
Step 1: The Foundation
Note: This sample project is available for download at MSDN Online (http://msdn.microsoft.com/en-us/library/ms771315(VS.85).aspx). The Windows SDK also features two additional UI Automation provider samples.
The foundation of UI Automation provider objects is the IRawElementProviderSimple interface implemented by the applications or by the UI Framework that supports UI Automation. Objects that contain a more complex tree structure should also implement IRawElementProviderFragment, and the root for this structure implements IRawElementProviderFragmentRoot. Objects whose performance is very important can use IRawElementProviderAdviseEvents to know which events they should raise. Finally, you should use the IRawElementProviderHwndOverride interface when the provider needs to reorder the automation tree. Most providers do not implement this interface.
In this project, you implement only IRawElementProviderSimple, which contains the following methods:
// IRawElementProviderSimple
STDMETHODIMP GetPatternProvider(
PATTERNID patternId, IUnknown** pRetVal);
STDMETHODIMP GetPropertyValue(
PROPERTYID propertyId, VARIANT* pRetVal);
STDMETHODIMP get_HostRawElementProvider(
IRawElementProviderSimple** pRetVal);
STDMETHODIMP get_ProviderOptions(
ProviderOptions* pRetVal);
For the UI Automation Framework to recognize your UI Automation provider implementation, you must respond to the WM_GETOBJECT message. The UiaReturnRawElementProvider function helps produce the key to establish a connection to the UI Automation Core:
case WM_GETOBJECT:
{
if (_pProxy == NULL)
{
// Create an automation element object
_pProxy =
CCheckbox3UIAProxy::Create(this);
}
return UiaReturnRawElementProvider(
_hwnd, wParam, lParam, _pProxy);
}
break;
Now you are ready to implement the support for UI Automation properties and pattern interfaces.
Step 2: Implementing the Properties and Control Patterns
UI Automation providers add support for properties with the GetPropertyValue method. For control patterns, providers support the corresponding patternId for the GetPatternProvider method.
Many accessibility practices are available in the form of industry standards (such as Section 508 of the Federal Rehabilitation Act).
You can register the properties and control patterns with GUIDs (such as the Toggle_Pattern_GUID) that you map to the corresponding IDs with the UiaLookupId function. For existing control patterns and properties, you can find corresponding IDs in UIAutomationClient.h (such as UIA_TogglePatternId).
In this example, you get the property value of a check box:
HRESULT CCheckbox3UIAProxy::GetPropertyValue(
PROPERTYID propertyId,
IUnknown **pRetVal)
{
// Clear out param
pRetVal->vt = VT_EMPTY;
// Specify ControlType.CheckBox for the
// ControlType property
if (propertyId == UIA_ControlTypePropertyId)
{
pRetVal->vt = VT_I4;
pRetVal->lVal = UIA_CheckBoxControlTypeId;
}
return S_OK;
}
To support controls with a binary status such as the check box (checked or cleared), the Toggle Pattern is best, and the implementation is straightforward as shown in Listing 1.
Here, you get the pattern provider for the toggle pattern:
HRESULT CCheckbox3UIAProxy::GetPatternProvider(
PATTERNID patternId,
IUnknown **pRetVal)
{
// Clear out param
*pRetVal = NULL;
if (patternId == UIA_TogglePatternId)
{
*pRetVal =
static_cast<IRawElementProviderSimple*>(this);
AddRef();
}
return hr;
}
Part II: Using Dynamic Annotation
Now, I will talk about using Dynamic Annotation for Win32® controls with relatively minor modifications. The technique was originally introduced with Microsoft Active Accessibility version 2.0. Its purpose is to enable a custom control to expose accessibility information without having to fully implement the IAccessible interface.
If you customize a standard control, try not to overload it with features. Unnecessary complexity can result in expensive accessibility support cost, not to mention reduced usability.
With Windows 7 Automation API, you can now use Dynamic Annotation to override either Microsoft Active Accessibility or UI Automation properties of the proxy objects that OLEACC creates for standard Windows controls (many of those supported by COMCTL32.dll or USER32.dll). If your custom control is a slight modification of an existing control or requires support for only a few accessibility properties, Dynamic Annotation is a good candidate for implementing your accessibility solution.
Dynamic Annotation provides three different mechanisms for handling annotations: Direct Annotation, Value Map Annotation, and Server Annotation. In this article I discuss only Direct Annotation and Server Annotation.
A sample Dynamic Annotation project is available for download at MSDN Online.
How It Works
Dynamic Annotation allows you to modify some of the Microsoft Active Accessibility properties, but not every Microsoft Active Accessibility property is relevant. The supported properties are:
- Name (PROPID_ACC_NAME)
- Description (PROPID_ACC_DESCRIPTION)
- Role (PROPID_ACC_ROLE)
- State (PROPID_ACC_STATE)
- Help (PROPID_ACC_HELP)
- KeyboardShortcut (PROPID_ACC_KEYBOARDSHORTCUT)
- DefaultAction (PROPID_ACC_DEFAULTACTION)
While annotating many of these Microsoft Active Accessibility properties is straightforward, be careful when you annotate the Microsoft Active Accessibility Role property. Annotation of Role won’t affect the behavior of the control or proxy object. For example, do not annotate a button as a slider because the baseline Win32 button control may not offer all of the functionality of a slider control.
To annotate a control, create an AccPropServices COM object, call the appropriate SetHwndProp methods to annotate the control, and then release the AccPropServices object. Do not forget to call CoInitializeEx to support all COM practices in your WinMain routine (Listing 2).
All annotations made in a Microsoft Active Accessibility property are reflected in the UI Automation translation as well as in the Microsoft Active Accessibility-to-UI Automation Proxy. If you want to override or add a UI Automation property to the control, you can specify a UI Automation Identifier GUID instead of the Microsoft Active Accessibility PropID.
Other Considerations: WinEvents
Applications and the UI Framework use WinEvents to notify accessibility applications of changes in the user interface. When one of the following happens, call NotifyWinEvent:
- An object is created, destroyed, shown, hidden, reordered, or invoked.
- A selection is changed.
- A Name, Value, State, Description, Location, Parent, Help, DefaultAction, or Accelerator accessibility property changes in an object.
These calls are needed only for objects for which you implement accessibility. The system provides accessibility for common objects and will call the appropriate events when needed. Not all annotations require an additional WinEvent.
Part III: Implementing IAccessibleEx
If you already have a solid Microsoft Active Accessibility implementation of a control, you can use the IAccessibleEx interface to add more accessibility functionality. The advantage of this interface is that you can reuse existing accessible object implementations, properties, object tree structures, and WinEvents. As long as the baseline Microsoft Active Accessibility implementation is clean, IAccessibleEx is an attractive solution to enhance the custom control for UI Automation.
… you can now use Dynamic Annotation to override Microsoft Active Accessibility or UI Automation properties.
To extend your Microsoft Active Accessibility implementations with the IAccessibleEx interface, do the following:
- Implement IAccessibleEx. Listing 3 shows the implementation of the GetObjectForChild and GetIAccessiblePair methods so UI Automation can map an IAccessible and ChildId pair to a corresponding IAccessibleEx instance.
- Expose IAccessibleEx with IServiceProvider::QueryService.
- Implement IRawElementProviderSimple to provide property values and pattern providers for your control.
- Implement control patterns and properties that describe your control.
- Implement WinEvents for your control.
A sample IAccessibleEx project is available for download at MSDN Online. The sample project has baseline accessibility support for a custom List Box control and the UI Automation RangeValue control pattern.
Conclusions and Resources
While there are many ways to make custom controls accessible, choosing the right solution can be tricky. It is easy to underestimate the cost of customizing controls. Be aware of the hidden costs of a complex UI design, and make sure you have a plan for making your wonderful new controls accessible to everyone.
Designing a UI Automation object model for a new user interface is straightforward if you follow few basic principles.
The examples introduced in this article are available for download from the MSDN article, “Making Custom Controls Accessible.” (http://msdn.microsoft.com/en-us/accessibility/cc307845.aspx) A more thorough discussion of this topic is available on the MSDN Accessibility Development Center (http://msdn.microsoft.com/en-us/accessibility/).