Steps for customizing the accessible name of a standard control, for five UI frameworks


This post describes steps to customize the accessible name of a standard control for the following types of apps:

 

1. XAML Store app

2. HTML Store app

3. Win32 app

4. WinForms app

5. WPF app

 

Introduction

A dev recently asked me how she could change the accessible name of a radio button in her UI, such that the accessible name was something other than the text shown visually for the radio button. This was an unusual thing to want to do, because you’d typically want the radio button’s accessible name to match the visuals. And many UI frameworks will provide that accessible functionality for you by default.

But in this particular case, it just so happened that the app UI contained two groups of radio buttons. A radio button from one of the groups displayed exactly the same text as one of the radio buttons from the other group. This meant that the accessible names of the two radio buttons were the same. The simplest way to avoid that would be to change the visual text shown on one of the radio buttons. If the visual text shown on the radio buttons is different, then by default the accessible names will be different too. But that approach was not an option for this app, for not-technical reasons.

Technically it might be possible for your customer who uses a screen reader to learn about the radio buttons’ associated grouping when they reach the radio buttons. After all, some group header text might be shown somewhere in the UI. But in this case, the dev wanted to override the radio buttons’ accessible names, in order to differentiate them.

The UI framework being used in this case was Win32, and so I supplied a snippet to the dev which would set custom accessible names on the radio buttons. But this got me thinking about how I would achieve the same results with other UI frameworks. If someone wanted to do this in any of a XAML Store app, an HTML Store app, or in Win32/WinForms/WPF apps, what would the steps be?

So this post describes some related steps in all these frameworks. It does not include details for UI frameworks which don’t build up some level of programmatic accessibility by default. For example, DirectX. It also doesn’t detail MFC, but given that you can access the hwnds used in an MFC app, the steps described below for Win32 should work fine in an MFC app.

In order to test out my code snippets, I made up a scenario where the UI showed two radio button groups. The first group is for selecting a country from a list of countries which have interesting health care systems. The second group is for making a selection from a list of types of geese. So I end up with both groups containing radio buttons whose visual text is “Canada”. As such, I’ll change the accessible names to be “Canada the country” and “Canada goose”.

Needless to say, this was a rather different scenario from the one that the original dev had in her own app, but you get the idea.

 

Important: While it was a question relating to radio buttons that prompted me to write this post, the steps apply to customizing the accessible names of many other standard control types too. This includes controls like a ComboBox or Edit control, which in many UI frameworks won’t get an accessible name by default.

 

 

A hypothetical app which shows two radio buttons, both with associated visual text of “Canada”.

Figure 1: A hypothetical app which shows two radio buttons, both with associated visual text of “Canada”.

 

Does the Narrator screen reader have to account for an app’s UI framework?

Narrator finds out all it needs to know about an app, through the Windows UI Automation (UIA) client API. The app process containing all the UI of interest to the user, is known as the “provider”. Which component is doing the work in the provider process to build up the programmatic representation of the app’s UI is of no interest to Narrator. That provider could be a XAML, HTML, Win32, WinForms or WPF app, but all Narrator cares about is the programmatic representation of the UI that’s exposed through the UIA API.

 

A tip on learning where UIA data is coming from

Despite having just said that Narrator doesn’t care which component in the provider process is generating the UIA data, sometimes I do care. The steps taken by an app dev to customize the programmatic representation of an app’s UI often depend on which UI framework is used by the app.

So whenever someone points me to UI and asks how they can achieve some particular action around customizing the app’s programmatic accessibility, one of the first things I wonder is, what UI framework are we dealing with here? A quick way for me to get some idea of the answer, is for me to point the Inspect SDK tool to the UI, and check out the UIA ProviderDescription property. That property is a long and mysterious string, but there may be something near the end of it that tells me what I need to know.

Say I point the Inspect SDK tool to a button shown in the UI in the Calculator app. The ProviderDescription includes “Windows.UI.Xaml.dll”. That tells me that the UIA provider for the button is the XAML framework. (Note that the ProviderDescription doesn’t include any mention of custom AutomationPeers if a XAML app uses them.)

And as I point the Inspect SDK tool to parts of other apps, I can see what provider is generating the UIA data for that UI.

 

XAML Store app - “Windows.UI.Xaml.dll”

Start Menu tile - “Windows.UI.Xaml.dll”

Web page in Edge - “edgehtml.dll”

Windows 10 HTML Store app - “edgehtml.dll”

Visual Studio 2015 - “MS.Internal.Automation.ElementProxy, PresentationCore”

Outlook 2016 - “mso40uiwin32client.dll”

Paint’s ribbon UI - “MSAA Proxy, UIAutomationCore.DLL”

 

You can find a mix of UIA providers when exploring UI across different products. Some providers, like Outlook 2016’s mso40uiwin32client.dll is a native UIA provider, built for Outlook. Other UIA providers might be a part of the UI framework used to build a feature. For example, the Windows 10 Start Menu leverages the UIA provider included in the XAML framework. And Visual Studio 2015 leverages the UIA provider available through WPF’s PresentationCore.dll. The Edge browser has a native UIA provider, and that provider is also used by Windows 10 HTML Store apps.

It’s worth a comment on the “MSAA Proxy” listed above. Some older UI supports the legacy MSAA accessibility API, and has not been updated to implement the UIA provider API. Yet a UIA client app like Narrator must be able to interact with the UI through the UIA API. In those cases, where practical UIA itself will map the MSAA data being exposed by the UI, into UIA data for the UIA client. That work is being done by UIA’s MSAA Proxy. In that situation, the ProviderDescription will include “MSAA Proxy” somewhere in it.

 

What about the UIA FrameworkId property?

Checking the FrameworkId property associated with UI can also be somewhat interesting. For example, if I look at a button in the Calculator app or a tile in the Start Menu, the Inspect SDK tool tells me that the FrameworkId is “XAML”. And if I look at Visual Studio 2015, I get told the FrameworkId is “WPF”. That seems easier to digest than the ProviderDescription string right? But if I point the Inspect SDK tool to a page hosted in the Edge browser or to a Windows 10 HTML Store app, I get told the FrameworkId is “Internet Explorer”, just as I would if I looked at a page hosted in the Internet Explorer browser itself. The Edge provider is not the same provider as that used by Internet Explorer.  So while the FrameworkId might be interesting at times, I find the ProviderDescription more useful. (And besides, sometimes UI exposes no FrameworkId property at all.)

 

Overriding the accessible name of a radio button

 

XAML Store app

A very tempting thing to do when overriding an accessible name in a XAML app, is to explicitly set the AutomationProperties.Name of the control in your XAML. After all, this would seem to work fine.

 

<RadioButton Content="Canada" AutomationProperties.Name="Canada goose" />

 

But just as I wouldn’t ship an app all over the world if it showed hard-coded English text visually, I wouldn’t ship an app with hard-coded English accessible names. Instead I’d add an x:Uid to the XAML, and then add both the localized visual text and localized accessible name to the string resource file.

 

<RadioButton x:Uid="canadaGooseRadioButton"/>

 

 

Fig1_XAML_Resources

Figure 2: Adding localizable strings in Visual Studio for the radio button’s accessible name and its displayed text.

 

I have to say, adding the folders required to support localization only takes a few seconds once you’ve done it a few times, and so it’s one of those things that’s always worth doing when starting on a new app. The alternative is to add “temporary” hard-coded strings in your XAML, and those always hang around longer than you’d like.

 

Adding the English (US) localized string file to the XAML app solution in Visual Studio.

Figure 3: Adding the English (US) localized string file to the XAML app solution in Visual Studio.

 

The end result is that when I point the Inspect SDK tool to the radio button, I find its accessible name is not the same as the text shown visually on the radio button.

 

Adding the English (US) localized string file to the XAML app solution in Visual Studio.Inspect reporting the accessible name of the XAML radio button.

Figure 4: Inspect reporting the accessible name of the XAML radio button.

 

 

HTML Store app

Back on the subject of what’s tempting, sometimes in HTML Store apps it can be tempting to used ARIA markup to control the programmatic accessibility of an element. But it’s strongly recommended that ARIA isn’t to be used unless it’s really necessary.

So while the “aria-label” markup would allow you to override the accessible name of a button, that’s not what we’re going to do here. Instead we’ll create a hidden label, and have that be leveraged as the accessible name of the radio button.

 

<input type="radio" value="Canada" id="canadagoose">Canada

<label for="canadagoose" hidden="true">Canada goose</label>

 

I can then point the Inspect SDK tool to the radio button and verify that it has the expected custom accessible name.

Of course, the above snippet breaks my rule about not using hard-coded English strings as accessible names of controls. So I can update this to have the accessible name picked up from a localized string instead.

 

<input type="radio" value="Canada" id="canadagoose">Canada

<label for="canadagoose" hidden="true"

    data-win-res="{textContent: 'CanadaRadioButtonAccessibleName'}" />

 

With the localized string in the string resource file, strings\en-us\resources.resjson.

 

{

    "CanadaRadioButtonAccessibleName" : "Canada goose"

}

 

 

Adding the English (US) localized string file to the HTML app solution in Visual Studio.

Figure 5: Adding the English (US) localized string file to the HTML app solution in Visual Studio.

 

 

I’ll load the string resources when my page gets activated.

 

var output;

WinJS.Resources.processAll(output);

 

I’ve not included the localization of the displayed text for the radio button, but localizing that is all part of your regular localization process.

Having done that, I can then point the Inspect SDK tool to the app and verify the accessible name of the radio button is as I expect.

 

Inspect reporting the accessible name of the HTML radio button.

Figure 6: Inspect reporting the accessible name of the HTML radio button.

 

 

Win32 app

Ok, so I’m assuming here that you have a regular Win32 CONTROL being used in your .rc file, with something like BS_AUTORADIOBUTTON specified. And so you just want to tweak the accessible properties of your standard Win32 radio button. A relatively straightforward thing to do here is to call SetHwndPropStr() with Name_Property_GUID, to say you want to set a custom UIA Name property on the control.

So you might do something like the following:

 

Near the top of your file…

 

#include <initguid.h>

#include "objbase.h"

#include "uiautomation.h"

IAccPropServices* _pAccPropServices = NULL;

 

When the radio button is created…

 

hr = CoCreateInstance(

    CLSID_AccPropServices,

    nullptr,

    CLSCTX_INPROC,

    IID_PPV_ARGS(&_pAccPropServices));

if (SUCCEEDED(hr))

{

    WCHAR szRadioButtonAccessibleName[MAX_LOADSTRING];

    LoadString(

        hInst,

        IDS_CANADAGOOSE,

        szRadioButtonAccessibleName,

        ARRAYSIZE(szRadioButtonAccessibleName));

 

    hr = _pAccPropServices->SetHwndPropStr(

        GetDlgItem(hDlg, IDC_RADIOBUTTON_CANADAGOOSE),

        OBJID_CLIENT,

        CHILDID_SELF,

        Name_Property_GUID,

        szRadioButtonAccessibleName);

}

 

When the UI is destroyed…

 

if (_pAccPropServices != nullptr)

{

    // We only added the one property to the hwnd.

    MSAAPROPID props[] = { Name_Property_GUID };

    _pAccPropServices->ClearHwndProps(

        GetDlgItem(hDlg, IDC_RADIOBUTTON_CANADAGOOSE),

        OBJID_CLIENT,

        CHILDID_SELF,

        props,

        ARRAYSIZE(props));

 

    _pAccPropServices->Release();

    _pAccPropServices = NULL;

}

 

With the above action, I can then point the Inspect SDK tool to the radio button, and verify that its accessible name is as I expect.

 

Inspect reporting the accessible name of the Win32 radio button.

Figure 7: Inspect reporting the accessible name of the Win32 radio button.

  

Note that SetHwndPropStr() and SetHwndProp() can be really helpful for setting other accessibility-related properties too. I mention how SetHwndProp() can help your customers using a screen reader to be notified of changes happening in your UI, at How to have important changes in your Win32 UI announced by Narrator.

 

 

WinForms app

A WinForms app makes it pretty straightforward to set a custom accessible name on a radio button, thanks to Control.AccessibleName. You could load up your localized string, and set that AccessibleName property. But personally, I’ve found that the Visual Studio’s UI localization process makes it even easier than that. As it happens, I went through this process a few weeks ago when I added a Dutch version to one of my apps.

For the purposes of this post, I just created a test WinForms app, added a radio button to it, and added a localized accessible name to the radio button. I did this by first going to the form’s properties, and setting the Localizable property to True. I then specified which languages I’m interested in.

 

Adding localized versions of a WinForms app to a solution in Visual Studio.

Figure 8: Adding localized versions of a WinForms app to a solution in Visual Studio.

 

I then opened up the form for each of the languages I’d specified, and went to the properties of my radio button. It was then straightforward to set the required localized accessible name on the radio button specific to the language of the form.

 

Setting the localized accessible name of the radio button for the form in a specific language.

Figure 9: Setting the localized accessible name of the radio button for the form in a specific language.

 

I can then point the Inspect SDK tool to the radio button and verify the accessible name is as I expect.

 

Inspect reporting the accessible name of the WinForms radio button.

Figure 10: Inspect reporting the accessible name of the WinForms radio button.

 

 

WPF app

The accessible name of the WPF radio button can be set through an AutomationProperties class, just as it can be with a XAML Store app, but in this case the AutomationProperties is picked up from System.Windows.Automation. (The XAML Store app picks it up from Windows.UI.Xaml.Automation.)

For a quick test, this worked fine:

 

<RadioButton

    Content="Canada"

    AutomationProperties.Name="Canada goose"

/>

 

But again this breaks the rule about not having hard-coded English accessible names. I had a go at referencing a localized string resource from markup, but got an exception at run-time. I must have missed something out, but I’ve limited experience with WPF and never build a localized WPF app. I expect the localization process for the accessible names is similar to that for text displayed visually.

I did make sure I could reference a localized string from code for the accessible name of the radio button.

 

AutomationProperties.SetName(

    CanadaGooseRadioButton,

    Properties.Resources.CanadaGooseRadioButtonAccessibleName);

 

Perhaps if I read WPF Globalization and Localization Overview, I’d learn where I went wrong in my attempt to reference the localized string in markup.

With the above loading of the localized string, I could point the Inspect SDK tool at the app and verify that the radio button’s accessible name is as I expect.

 

Inspect reporting the accessible name of the WPF radio button.

Figure 11: Inspect reporting the accessible name of the WPF radio button.

 

 

Summary

Overall it can be relatively straightforward to set custom localized accessible names on standard controls in your app. Often the UI framework will do a fine job at providing a useful accessible name by default, but every now and again you might feel that by setting a custom accessible name yourself, you’re providing a more efficient experience for your customer.

Hopefully the code snippets above can help you provide that experience, regardless of whether you’re building a XAML Store app, HTML Store app, or a Win32, WinForms or WPF app.

And you can just paste the snippets directly into your app if you happen to have UI which relates to both geese and interesting health care systems. 🙂

Guy


Comments (0)

Skip to main content