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); 
MSAAPROPID propids[1] = { PROPID_ACC_NAME }; 
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.

   

image

 

 

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);
    MSAAPROPID propid = PROPID_ACC_NAME;
    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);
                }
                else
                {
                    fSuccess = FALSE;
                }
            }
            else
            {
                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.