Win32: Setting the accessible name on a control

This article is referenced directly by Microsoft Accessibility Insights for Windows. Microsoft Accessibility Insights for Windows can help spotlight many accessibility issues in UI, and guide you to the relevant UI framework-specific code sample to help you resolve the issue. Learn more about Microsoft Accessibility Insights for Windows.

Problem

When a screen reader moves to the control, its announcement does not include the purpose of the control. As such, my customer may be blocked from completing their task.

Suggested Fix

The purpose of a control is conveyed through the control's Name property as exposed through the UI Automation (UIA) API. The Name property must be accurate, concise, unambiguous and localized. For some types of controls, the text shown visually on the control's is repurposed as its UIA Name property. If this is not the case with your control, (for example, the control type is one of ComboBox, Edit, Image, ListView or TreeView,) special attention must be paid to ensure an appropriate UIA Name property is exposed by the control.

If the control has an associated visual label which describes the purpose of the control, consider having the control's UIA Name property set automatically from the label's text. This approach avoids the need to add a localized string specifically for the accessible name of the control. To have the UIA Name set in this way, define the UI such that the label immediately precedes the control.

Note

This technique works even when steps are taken to prevent the label having any visual representation on the screen. For example, if the label is defined with "NOT WS_VISIBLE" in the .rc file, then Win32 will not expose the label visually or programmatically through UIA, yet still leverage it as the accessible name of the control that follows it. Note that typically the label is shown visually in order to provide the best possible experience for your sighted customers.

If the control has no associated label, consider adding a label in order for your sighted customers to efficiently learn of the purpose of the control. If that is not practical, consider explicitly setting an accessible name on the control through use of the SetHwndPropStr() function as shown below.

Code Sample 1: Setting the control's accessible name from a nearby label.

// By default, show a helpful label visually, and have the control immediately follow it
// in the UI definition. This will provide a helpful experience for customers regardless
// of whether they use screen readers or are sighted. 

// If for some reason the label is not to be shown visually, then it could be defined 
// with "NOT WS_VISIBLE" and sized to be small, and still have its text repurposed as 
// the accessible name of the control.

// Replace the example use of the edit control and ComboBox here with your specific type
// of control.

LTEXT "Spaniels:", IDC_STATIC, 10, 10, 30, 10
EDITTEXT IDC_EDIT_SPANIELS, 45, 10, 60, 12, WS_TABSTOP

LTEXT "Birds:", IDC_STATIC, 10, 24, 30, 10
COMBOBOX IDC_COMBOBOX_BIRDS, 45, 24, 60, 10, CBS_DROPDOWN | WS_VSCROLL | WS_TABSTOP

Code Sample 2: Setting the control's accessible name explicitly because it has no associated label.

Important: The following C++ code would only be used if for some reason it is not practical to have the control's accessible name set from an associated label, as shown in the previous code sample.

// At the top of the C++ file.
#include <initguid.h> 
#include "objbase.h"
#include "uiautomation.h" 
IAccPropServices* _pAccPropServices = NULL;


// Run when the UI is created.
void SetControlAccessibleName(HWND hDlg)
{
    HRESULT hr = CoCreateInstance(
        CLSID_AccPropServices,
        nullptr,
        CLSCTX_INPROC,
        IID_PPV_ARGS(&_pAccPropServices));
    if (SUCCEEDED(hr))
    {
        // Load up the localized accessible name for the control.
        WCHAR szName[MAX_LOADSTRING];
        LoadString(
            hInst,
            IDS_COMBOBOX_BIRDS,
            szName,
            ARRAYSIZE(szName));

        // Now set the name on the control. This gets exposed through UIA 
		// as the element's Name property.
        hr = _pAccPropServices->SetHwndPropStr(
            GetDlgItem(hDlg, IDC_COMBOBOX_BIRDS),
            OBJID_CLIENT,
            CHILDID_SELF,
            Name_Property_GUID,
            szName);
    }
}


// Run when the UI is destroyed.
void ClearControlAccessibleName(HWND hDlg)
{
    if (_pAccPropServices != nullptr)
    {
        // Clear the custom accessible name set earlier on the control.
        MSAAPROPID props[] = { Name_Property_GUID };
            
		_pAccPropServices->ClearHwndProps(
            GetDlgItem(hDlg, IDC_COMBOBOX_BIRDS),
            OBJID_CLIENT,
            CHILDID_SELF,
            props,
            ARRAYSIZE(props));

        _pAccPropServices->Release();
        _pAccPropServices = NULL;
    }
}