How to Use UI Automation to Make a Windowless ActiveX Control Accessible
Describes how to use the Microsoft UI Automation API to ensure that your windowless Microsoft ActiveX control is accessible to assistive technology (AT) client applications.
- C/C++
- Microsoft Win32 and Component Object Model (COM) programming
- Windowless ActiveX Controls
- UI Automation providers
To make your application accessible, you must implement the UI Automation provider interfaces for your windowless ActiveX control, including IRawElementProviderSimple, IRawElementProviderFragment, IRawElementProviderFragmentRoot, and IRawElementProviderAdviseEvents. You should implement these interfaces just as you would for a window-based control, except as described in the following steps. For more information about implementing UIA provider interfaces, see UI Automation Provider Programmer's Guide.
When a client needs accessibility information about your windowless control, the control container calls your control's IServiceProvider::QueryService method to retrieve the IRawElementProviderSimple interface pointer for your control.
The following example shows how to implement the QueryService method.
STDMETHODIMP CMyAccessibleUIAControl::QueryService(REFGUID guidService,
REFIID riid, void **ppvObject)
{
if (ppvObject == NULL)
{
return E_INVALIDARG;
}
*ppvObject = NULL;
HRESULT hr = E_FAIL;
if (guidService == __uuidof(IRawElementProviderSimple))
{
hr = QueryInterface(riid, ppvObject);
}
return hr;
}
When your windowless control’s IRawElementProviderFragment::Navigate method is called to navigate to the parent or a sibling of the windowless control's root provider, your Navigate method should delegate to the IRawElementProviderWindowlessSite::GetAdjacentFragment method of the control container.
The following example shows how to implement the Navigate method.
STDMETHODIMP CMyAccessibleUIAControl::Navigate(NavigateDirection direction,
IRawElementProviderFragment **ppRetVal)
{
if (ppRetVal == NULL)
{
return E_INVALIDARG;
}
*ppRetVal = NULL;
HRESULT hr = E_FAIL;
IRawElementProviderWindowlessSite *pWindowlessSite = NULL;
if (direction == NavigateDirection_Parent)
{
// Query the control container's windowless site
// for the parent.
if (SUCCEEDED(m_pClientSite->QueryInterface(
IID_PPV_ARGS(&pWindowlessSite))))
{
hr = pWindowlessSite->GetAdjacentFragment(direction, ppRetVal);
}
}
else if (direction == NavigateDirection_FirstChild)
{
// GetFragmentForChild is an application-defined function that
// retrieves the first or last child fragment.
hr = GetFragmentForChild(FIRST, ppRetVal);
}
else if (direction == NavigateDirection_LastChild)
{
hr = GetFragmentForChild(LAST, ppRetVal);
}
SafeRelease(&pWindowlessSite);
return S_OK;
}
When your windowless control receives a call to its IRawElementProviderFragment::GetRuntimeId method, the control must do the following:
- Retrieve a runtime ID prefix by calling the control site's IRawElementProviderWindowlessSite::GetRuntimeIdPrefix method.
- Create a unique runtime ID for the control by appending an integer to the runtime ID prefix.
- Return the runtime ID to the caller.
The following example shows how to implement the GetRuntimeId method.
STDMETHODIMP CMyAccessibleUIAControl::GetRuntimeId(SAFEARRAY **ppRetVal)
{
if (ppRetVal == NULL)
{
return E_INVALIDARG;
}
*ppRetVal = NULL;
HRESULT hr = E_FAIL;
IRawElementProviderWindowlessSite *pWindowlessSite = NULL;
if (SUCCEEDED(m_pClientSite->QueryInterface(IID_PPV_ARGS(&pWindowlessSite))))
{
// Create a safe array to hold runtime ID.
SAFEARRAY *psa = SafeArrayCreateVector(VT_I4, 1, 3);
if (psa == NULL)
{
hr = E_OUTOFMEMORY;
}
// Retrieve the runtime ID prefix from the control container. The prefix
// consists of UiaAppendRuntimeId followed by the windowless site ID.
if (SUCCEEDED(hr))
{
hr = pWindowlessSite->GetRuntimeIdPrefix(&psa);
}
if (SUCCEEDED(hr))
{
// Append this fragment's ID to the retrieved runtime ID prefix.
long i = 2;
hr = SafeArrayPutElement(psa, &i, (void*)&m_Id);
}
if (SUCCEEDED(hr))
{
*ppRetVal = psa;
}
}
SafeRelease(&pWindowlessSite);
return hr;
}