Making Custom Controls Accessible, Part 5: Using IAccessibleEx to Add UI Automation Support to a Custom Control
This is the fifth of five articles on making custom controls accessible. Before reading this article, please ensure that you have read and completed the steps in the Getting Started article.
A previous article in this series showed how to create a custom control that implements the IAccessible interface to enable access by Microsoft Active Automation (MSAA) clients. The control is a list box containing items that each include a value indicator similar to a progress bar. The user can manipulate the progress bar by selecting an item and using the arrow keys.
In an ideal world, all controls would fully implement interfaces for both MSAA and UI Automation. In practice, many existing controls could benefit from implementing some UI Automation functionality, but it is not practical to implement UI Automation completely.
In this article, we add accessibility features to the control by implementing the new IAccessibleEx interface, which in turn allows us to add partial support for UI Automation. By implementing UI Automation interfaces, the control can provide properties and functionality that are not available through IAccessible.
Note The definition of IAccessibleEx in the project headers is preliminary and subject to change. The final interface definition will be published in an update to the SDK header files.
Description of the Initial Project
The initial project is a slightly revised version of the final project from Article 3. It contains a custom control that has been made accessible through an implementation of IAccessible.
Although the custom control in the initial project is accessible through its implementation of IAccessible, that interface is generic and cannot report custom properties, such as the range or step values of the progress bar.
UI Automation providers, on the other hand, can implement interfaces that are specific to different kinds of controls. In this case, because each list item displays a value within a range, the IRangeValueProvider interface provides a way to expose the range and step values.
The IAccessibleEx interface serves as a bridge between an IAccessible implementation, such as that in our initial project, and a partial implementation of UI Automation.
The control does not have to implement UI Automation fully, because it is designed for use with MSAA clients that are operating in-process rather than UI Automation clients that are communicating with controls through the mediation of UIAutomationCore.dll. The control’s IAccessibleEx and IRawElementProviderSimple interfaces provide a direct link between the control’s UI Automation functionality and the client.
The support for UI Automation consists of the following:
1. Implementations of IRawElementProviderSimple for both the list box and the list items. This interface is primarily used to return properties not supported by IAccessible.
2. Implementations of any control pattern interfaces that can provide functionality not available through IAccessible. In our example, the only control pattern interface implemented is IRangeValueProvider, which is implemented on list box items. It is not necessary to implement ISelectionProvider or ISelectionItemProvider, because those interfaces do not provide much useful functionality beyond what is already available through IAccessible.
Updating the custom control requires these main steps:
- Implement IServiceProvider on the list box accessible object so that the IAccessibleEx interface can be found on this or a separate object.
- Implement IAccessibleEx on the list box accessible object.
- Create an accessible object for list items, which in MSAA are not objects but are accessed by their child IDs. Implement IAccessibleEx on this object.
- Implement IRawElementProviderSimple for the list box and list items.
- Implement IRangeValueProvider on the list item accessible object.
Step 1: Expose IAccessibleEx
Because the implementation of IAccessibleEx for a control may reside in a separate object, client applications cannot rely on QueryInterface to obtain this interface. Instead, clients are expected to call IServiceProvider::QueryService. The implementation of this method for the example list box simply calls through to QueryInterface, because the list box accessible object itself implements IAccessibleEx. The following code shows the implementation of QueryService:
HRESULT CListboxAccessibleObject::QueryService(REFGUID guidService, REFIID riid, LPVOID *ppvObject)
*ppvObject = NULL;
if (guidService == __uuidof(IAccessibleEx))
return QueryInterface(riid, ppvObject);
Step 2: Implement IAccessibleEx on the List Box
The method of IAccessibleEx that is of most interest is GetObjectForChild. This method gives us an opportunity to create an accessible object (one that exposes, at a minimum, IAccessibleEx) for a list item. Remember that in our implementation of MSAA, the list items do not have their own IAccessible interface but are treated as children of the list box accessible object. The following code shows the implementation of GetObjectForChild:
long idChild, IAccessibleEx **pRetVal)
int x = sizeof(long);
vChild.vt = VT_I4;
vChild.lVal = idChild;
HRESULT hr = ValidateChildId(vChild);
// List item accessible objects are stored as an array of pointers;
// for purposes of the example it is assumed that the list contents
// will not change. Accessible objects are created only when needed.
if (itemProviders[idChild - 1] == NULL)
// Create an object that supports UI Automation and
/// IAccessibleEx for the item.
itemProviders[idChild - 1] =
new CListItemAccessibleObject(idChild, g_pListboxControl);
if (itemProviders[idChild - 1] == NULL)
IAccessibleEx* pAccEx = static_cast<IAccessibleEx*>
(itemProviders[idChild - 1]);
if (pAccEx != NULL)
*pRetVal = pAccEx;
See the notes in the sample code for descriptions of the other methods of IAccessibleEx.
Step 3: Implement IAccessibleEx on the List Items
For the list item accessible object, the main role of IAccessibleEx is to provide a means of working backward from the object to the parent IAccessible, which is returned by the CListItemAccessibleObject::GetIAccessiblePair method along with the child ID used to identify the list item in an IAccessible implementation. The following code shows the implementation of GetIAccessiblePair:
IAccessible **ppAcc, long *pidChild)
if (!ppAcc || !pidChild)
CListboxAccessibleObject* pParent = m_control->GetAccessibleObject();
HRESULT hr = QueryInterface(__uuidof(IAccessible), (void**)ppAcc);
*pidChild = 0;
*pidChild = m_childID;
Step 4: Implement IRawElementProviderSimple for the List Box and List Items
Only one method of IRawElementProviderSimple is of interest for the list box. This is GetPropertyValue, which enables the control to return properties specific to UI Automation. In the example project, a single property is supported as an illustration: a localized description of the custom control. (For simplicity, the string is hard-coded rather than taken from a resource file.)
The property identifiers used by UI Automation are GUIDs that have to be retrieved by using the UiaLookupId function, found in UIAutomationCore.lib and UIAutomationCore.h. The following code shows the implementation of GetPropertyValue:
HRESULT STDMETHODCALLTYPE CListboxAccessibleObject::GetPropertyValue(PROPERTYID propertyId,
if (pRetVal == NULL)
HRESULT hr = CheckAlive();
if (propertyId == AutoIds.LocalizedControlTypeProperty)
pRetVal->vt = VT_BSTR;
pRetVal->bstrVal = SysAllocString(L"CustomSliderList");
if (pRetVal->bstrVal == NULL)
pRetVal->vt = VT_EMPTY;
// Else pRetVal is empty, and UI Automation will attempt
// to get the property from the HostRawElementProvider,
// which is the default provider for the HWND.
pRetVal->vt = VT_EMPTY;
The list item accessible object has a similar implementation of GetPropertyValue. In addition, because we want list items to be able to return pattern-specific properties, we must also implement GetPatternProvider. The implementation simply returns a reference to the IRangeValueProvider interface, or NULL if another interface is requested. The following code shows the implementation of GetPatternProvider:
HRESULT STDMETHODCALLTYPE CListItemAccessibleObject::GetPatternProvider(
PATTERNID patternId, IUnknown** pRetVal)
if (pRetVal == NULL)
if (patternId == AutoIds.RangeValuePattern)
*pRetVal = static_cast<IUnknown*>
*pRetVal = NULL;
Step 5: Implement IRangeValueProvider on the List Item Accessible Object
Control pattern interfaces need to be implemented only if they add functionality that is not provided by IAccessible. However, all methods of such interfaces should be implemented. For example, IAccessible enables control values to be read, so the implementation of IRangeValueProvider::get_Value is not used by MSAA clients. However, other clients might expect to find this method implemented.
Our implementation of IRangeValueProvider is mostly intended to supply property values not otherwise available, namely the minimum and maximum values of the control, and the amount by which the value is incremented or decremented when the user presses an arrow key. The following code shows a typical method for getting the maximum value of the control:
HRESULT STDMETHODCALLTYPE CListItemAccessibleObject::get_Maximum(
*pRetVal = m_control->GetMaxValue();
Description of the Final Project
When examining the custom control, you will now see that additional properties are displayed: the localized control type for both the list box and the list items, and the custom properties of the slider.
The following UI Automation AutomationElement properties do not overlap with any MSAA properties, and can be used in an IAccessibleEx implementation:
The following properties have some overlap with MSAA properties, and so can be used in an IAccessibleEx implementation, with the specified caveats:
- AcceleratorKey, AccessKey: These overlap with accKeyboardShortcut; but can be provided if a control has both an access key and an accelerator.
- ControlType: This overlaps with accRole, but can be specified to provide a more specific role.
The properties in the following table are already covered by MSAA properties and do not need to be supported by an IAccessibleEx implementation.
|ProcessId||Provided by core UI Automation|
|RuntimeId||Provided by core UI Automation|
The following UI Automation control patterns do not have to be implemented when the control has one of the roles outlined in the following table. Other control patterns should be supported if relevant.
|Control pattern||Corresponding MSAA roles|
|InvokePattern||ROLE_PUSHBUTTON, ROLE_MENUITEM, ROLE_BUTTONDROPDOWN, ROLE_SPLITBUTTON, any other role where there is a default action.|
|ValuePattern||ROLE_TEXT when not read-only; ROLE_PROGRESSBAR, ROLE_COMBOBOX, or any other role where accValue is valid.|
|WindowPattern||Automatically supported on top-level Win32 HWNDs.|
List of All Articles