Skip to Main Content
Perplexed Owl Random Ponderings

Benjamin ‘Benilda’ Key:

July 22, 2019; September 15, 2022; March 21, 2023

Using Direct Annotation in C++

Sometimes you will encounter a situation in which the existing accessibility support for an user interface element is almost good enough and you need to just make small changes to the existing support. For example, perhaps you just need to override the Name or Role properties. In these cases, fully implementing the IAccessible interface as described in Implementing a Microsoft Active Accessibility (MSAA) Server Using the Windows API and C++ may seem like a great deal of effort to achieve these improvements.

Fortunately, starting with Windows 7 there is an easier alternative, the Direct Annotation feature of the Dynamic Annotation API. The Dynamic Annotation API is extension to Microsoft Active Accessibility and UI Automation that makes it possible to customize existing IAccessible support without using subclassing or wrapping techniques. Direct Annotation is one of several features provided by the Dynamic Annotation API; it is the simplest of the features provided.

Direct Annotation makes it possible to customize the following MSAA properties.

Since Name, Role, Value, and State are included in the list of properties supported by the Direct Annotation feature, this feature is sufficient to meet WCAG Success Criterion 4.1.2: Name, Role, Value.

Direct Annotation also makes it possible to override or add a Microsoft UI Automation property to a control by specifying a UI Automation Property Identifier instead.

Implementation Details

The first step to using the Direct Annotation feature of the Dynamic Annotation API is to create an IAccPropServices object using either the CoCreateInstance function or CoCreateInstanceEx function. Then you can use either the IAccPropServices::SetHwndProp method or the IAccPropServices::SetHwndPropStr method.

The following sample from Using Direct Annotation demonstrates the technique.


HRESULT CMyTextControl::SetAccessibleProperties()
{
    // COM is assumed to be initialized...
    IAccPropServices* pAccPropServices = NULL;
    HRESULT hr = CoCreateInstance(CLSID_AccPropServices, nullptr, CLSCTX_SERVER,
        IID_IAccPropServices, reinterpret_cast<void**>(&pAccPropServices));
    if (SUCCEEDED(hr))
    {
        // Annotating the Role of this object to be STATICTEXT
        VARIANT var;
        var.vt = VT_I4;
        var.lVal = ROLE_SYSTEM_STATICTEXT;
        hr = pAccPropServices->SetHwndProp(_hwnd, OBJID_CLIENT, CHILDID_SELF, PROPID_ACC_ROLE, var);
        pAccPropServices->Release();
    }
    return hr;
}

More complete examples can be found in the following locations.

Note that using the IAccPropServices::SetHwndProp method or the IAccPropServices::SetHwndPropStr method will not send WinEvents. It is your responsibility to send appropriate events by calling the NotifyWinEvent function after the IAccPropServices::SetHwndProp method or the IAccPropServices::SetHwndPropStr method has been called.

For example, if you use IAccPropServices::SetHwndPropStr method to set the Name property of an element, send an EVENT_OBJECT_NAMECHANGE event for that object after SetHwndPropStr returns.

The CWndWithCustomizedAccessibleInfo class and DirectAnnotation class both make the necessary calls to the NotifyWinEvent function for you.

Back to top