Share via


Win32: Setting the accessible name on a button

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 button, its announcement does not include the purpose of the button. 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. In some situations, specific action must be taken to have a helpful UIA Name property exposed for a Win32 button control. For example, when the button shows an image or a glyph, rather than a string which can be repurposed to be the UIA Name of the button.

In some cases, the text property of the button control is exposed as the UIA Name property, so consider first whether that approach works with your button.

If the button exposes an empty string for its UIA Name property by default, then if it has an associated visual label which describes the purpose of the button, consider having the button'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 button. To have the UIA Name set in this way, define the UI such that the label immediately precedes the button.

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.

If the button has no associated label, consider explicitly setting an accessible name on the button through use of the SetHwndPropStr() function as shown below.

Code Sample 1: Setting an owner drawn button's accessible name from the text property of the control.

This sample works with an owner drawn button.

// The UIA Name property of this button is "Download".
CONTROL "Download", IDC_BUTTON_DOWNLOAD, "Button", BS_OWNERDRAW | WS_GROUP | WS_TABSTOP, 10, 10, 20 10

Code Sample 2: Setting the button's accessible name from a nearby label.

Note that this example specifically chooses to not visually show the label that is being used to provide the accessible name for the button.

// Add a label immediately before the button in the UI. Define the label with 
// "NOT WS_VISIBLE" and size it to be small, and still have its text repurposed as 
// the accessible name of the button.

LTEXT "Download", IDC_STATIC, 8, 10, 0, 0, NOT WS_VISIBLE
CONTROL "", IDC_BUTTON_DOWNLOAD, "Button", WS_VISIBLE, 10, 10, 100, 10

Code Sample 3: Setting the button's accessible name explicitly on the control.

// 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 button. Note that the name 
		// should not include the control type, so the name would not include the 
		// text "button" in this case.
        WCHAR szName[MAX_LOADSTRING];
        LoadString(
            hInst,
            IDS_BUTTON_DOWNLOAD_NAME,
            szName,
            ARRAYSIZE(szName));

        // Now set the name on the button. This gets exposed through UIA as the 
		// element's Name property.
        hr = _pAccPropServices->SetHwndPropStr(
            GetDlgItem(hDlg, IDC_BUTTON_DOWNLOAD),
            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 button.
        MSAAPROPID props[] = { Name_Property_GUID };
            
		_pAccPropServices->ClearHwndProps(
            GetDlgItem(hDlg, IDC_BUTTON_DOWNLOAD),
            OBJID_CLIENT,
            CHILDID_SELF,
            props,
            ARRAYSIZE(props));

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