Using Dynamic Annotation with Child IDs

The Dynamic Annotation API in the Windows Automation API is a convenient way to make simple accessibility changes to the accessible properties of Win32 Common Controls without writing a lot of code. There is good reference documentation available on MSDN, and some samples, too. That said, I’ve received some questions about it and wanted to talk specifically about how it works with Child IDs.

A quick refresher

The basic usage model for Dynamic Annotation is simple. Suppose I have a static control in my application with an image on it instead of a text label, and I need to set the accessible name to be “Picture of a thermometer” in order to allow a screen reader to read the control correctly. No problem: my app uses CoCreateInstance() to create an AccPropServices object, and calls IAccPropServices::SetHwndPropStr() to annotate the name property (PROPID_ACC_NAME):


HWND hwndIcon = GetDlgItem(hwnd, IDC_ICON1); 
CHECKHR(pAccPropSvc->SetHwndPropStr(hwndIcon, static_cast<DWORD>(OBJID_CLIENT), CHILDID_SELF,
PROPID_ACC_NAME, L"Picture of a thermometer"));

In this code snippet, CHECKHR is just a macro that I’m using to log any errors that occur.  A real application would use an error handling strategy consistent with what it was using for the rest of the codebase.

I make sure to call IAccPropServices::ClearHwndProps() afterwards to clear off the property:

HWND hwndIcon = GetDlgItem(hwnd, IDC_ICON1); 
CHECKHR(hr = pAccPropSvc->ClearHwndProps(hwndIcon, static_cast<DWORD>(OBJID_CLIENT), CHILDID_SELF, 
propids, 1));

The samples I mentioned above cover this well.

One new extension, added in Windows 7, is that I can now annotate with UIA properties. The property IDs for UIA properties are all in UIAutomationCoreApi.h (in the Platform SDK). So, if I want to set new UIA properties like AutomationId or ItemStatus, I’ve got a good way to do that:

CHECKHR(pAccPropSvc->SetHwndPropStr(hwndIcon, static_cast<DWORD>(OBJID_CLIENT), CHILDID_SELF, 
AutomationId_Property_GUID, L"ThermometerAutomationId"));

How do I see if this is working?  I can use the Inspect tool, available in the Windows SDK, to inspect the properties on my annotated object and verify that the new properties are showing up.





Annotating child items

Suppose my application uses a standard menu (CreateMenu()) and I want to add some special accessibility properties to menu items. This is a little different than our button example: we don’t want to annotate the whole menu, but rather one of its children. Well, I can do that, too – I just use IAccPropServices::SetHmenuPropStr() and IAccPropServices::ClearHmenuProps(). They both take a child ID argument, which specifics which menu item to annotate. The Inspect tool can show you the child ID of the menu item. If you are just annotating one or two menu items, this is a good approach. This technique works on toolbars as well.  In this code snippet, the child ID is 1.

// Annotate the menu item with a string... 
CHECKHR(pAccPropSvc->SetHmenuPropStr(g_hMenuBar, 1, PROPID_ACC_NAME, L"NewText"));

However, It often happens that you want to annotate the properties of all of the children of a container in a certain way.  For example, suppose I have an owner-drawn menu and I want to set the accessible names for all of the items in the menu, even though the menu items don’t officially have any textual content.  Calling SetHmenuPropStr() for every child ID would be tedious and inflexible – if the menu structure changes, I have to remove all the annotations and reset them.  A better solution would be to use a callback function that can be called with a child ID to request the accessible name (or another property) for that child ID.  The MSDN sample for Dynamic Annotation demonstrates this in a formal way; the core idea is to create an object that implements IAccPropServer and use dynamic annotation to mark the HWND or HMENU with the IAccPropServer, rather than with a string.

The annotation looks very much as it did before:

    AutoRelease<MenuAccServer *> pMenuAccSrv = new MenuAccServer(pAccPropSvc);
    CHECKHR(pAccPropSvc->SetHmenuPropServer(g_hPopup, CHILDID_SELF, &propid, 1, pMenuAccSrv, ANNO_CONTAINER));


The essential function in MenuAccServer looks like this:

    BOOL GetPropValue(
        const BYTE *    pIDString,
        DWORD           dwIDStringLen,
        MSAAPROPID      idProp,
        VARIANT *       pvarValue)
        HMENU hmenu = NULL;;
        DWORD idChild = 0;
        BOOL fSuccess = TRUE;

        // Break down the identity string into components and check the idChild
        if(S_OK != _pAccPropSvc->DecomposeHmenuIdentityString(pIDString, dwIDStringLen,
                    &hmenu, &idChild) ||
            idChild == CHILDID_SELF)
            fSuccess = FALSE;

        // Respond to requests for PROPID_ACC_NAME with the override
        // accessible name
        if (fSuccess)
            if (idProp == PROPID_ACC_NAME)
                if ((idChild - 1) < ARRAYSIZE(g_ColorInfo))
                    pvarValue->vt = VT_BSTR;
                    pvarValue->bstrVal = SysAllocString(g_ColorInfo[ idChild - 1 ]._pName);
                    fSuccess = FALSE;
                fSuccess = FALSE;

        return fSuccess;

And with that, I can mark up all of the children of the HMENU differently depending on their child IDs.  Very convenient.

Don’t forget to clear the annotation when the HWND or HMENU is destroyed – leaving stale annotations around is a good way to cause memory leaks or crashes during shutdown.

       propid = PROPID_ACC_NAME;
       CHECKHR(pAccPropSvc->ClearHmenuProps(g_hPopup, CHILDID_SELF, &propid, 1));

So, if you have an accessibility problem with a common control with children, now you know a useful technique to add or override accessibility properties on the control.

Comments (6)

  1. Alexander says:

    Hi Michael,

    as there is no post from you about your UIACOMWrapper, I'm writing here: I posted in the discussion 'Problem accessing patterns' on the project page a link to archive with two projects.

    One project is build with standard UIAutomationClient, the other – with your wrapper (the version for Windows 8 CP). In the latter case, part of traditional patterns are inaccessible.

    Maybe, I'm wrong here and I need simply do something what I'm not aware of. Anyway, the case 'simply use wrapper instead of the standard library' doesn't work for me. :(

    if you'd be so kind to investigate into the issue, I would help testing the improved solution.


  2. oyunlarınasılaça7biliz says:

    ojıuygfvcxds<afxddbn bcfdvxn vzxvc fldskvalıkngvbjgıh jmgkıcmxdöıu jmnumftlok jfdıgdjkj8uny g hjuıhjvybgnklcı9htjdkxuhcdgkıjcfuıglrhıknmjgbhkjcfgudxjkgıfhjykıfjcgbjjkuhbgjbyugjgbbtbgbftrgbfg

  3. Jerry says:

    Hi Michael,

    I'm trying the approach of direct annotation.  My environment is VS2010/Window 8 64 bit OS.  There was linker error: "error LNK2001: unresolved external symbol _CLSID_AccPropServices … ".  I included "UIAutomationCore.lib;%(AdditionalDependencies)".  

    What's the minimum library/header required for an MFC dialog based application to include this feature?



  4. Jerry says:

    Can you share your sample projects?  So we could try them out on our own if possible.

  5. MSBernstein says:

    Hi, Jerry.  I linked to some samples at the very top of the article.

    For CLSID_AccPropServices: This article describes how to make sure your GUIDs from interface files get defined properly:…/130869



  6. Alex says:

    This solution is not working for me, NITGUID.H is not helping, as well as OBJBASE.H.