Common approaches for enhancing the programmatic accessibility of your Win32, WinForms and WPF apps (2020 Remaster)
The Accessibility Insights for Windows tool reporting that the UIA HelpText property of a button matches a visual label's text near the button.

Common approaches for enhancing the programmatic accessibility of your Win32, WinForms and WPF apps (2020 Remaster)

This article describes some common steps for addressing programmatic accessibility bugs in apps built with a number of types of Windows desktop UI. 

Introduction

Three years ago, I wrote a series of articles describing common steps relating to making some types of Windows desktop apps accessible. The series started at Common approaches for enhancing the programmatic accessibility of your Win32, WinForms and WPF apps: Part 1 – Introduction. I’d recommend you read that introductory article, as I’m not duplicating all of that content here.

I’m pretty sure that I’d used all the steps I described in that series of articles as part of helping app builders fix accessibility bugs in their own apps. What’s more, I think the steps relate to the vast majority of programmatic accessibility bugs that Windows desktop apps might exhibit.

And to be clear, I’m only talking about programmatic accessibility here. So the steps don’t relate to such things as keyboard accessibility, use of color, suppression of animations, and timeouts for transient visuals. Or designing an inclusive app in the first place. But if your app doesn’t ship with a usable programmatic interface, then some of your customers will be blocked from completing their tasks. It’s as simple as that.

Given that a few details in my original series are now stale, I thought I’d summarize things again here, with a few updates. And I have to say, I’m very pleased indeed that some helpful accessibility enhancements in the .NET Framework rendered my original article stale. Thank you .NET!

And as a reminder of what prompted me to write my original article, it all stemmed from someone telling me they’d tried to fix a bug using some accessibility-related class, and couldn’t figure out how to make it work. The answer was that the accessibility-class they were trying to use was part of another UI framework.

So here’s my original big quiz again, with a few more terms in the list:

Which of the terms below relate to which of Win32, WinForms, WPF, UWP XAML and HTML?

  • AccessibilityNotifyClients
  • AccessibilityObject
  • AccessibleEvents
  • AccessibleName
  • ARIA
  • AutomationEvents
  • AutomationPeer
  • AutomationProperties
  • IAccessible
  • IAccessible2
  • IAccessibleEx
  • IRawElementProviderSimple
  • IUIAutomation5
  • LiveSetting
  • NotifyWinEvent
  • RaiseAutomationEvent
  • RaiseLiveRegionChanged
  • role
  • SetHwndPropStr
  • UiaRaiseAutomationEvent


If you can answer all that correctly without looking the terms up, you do better than I do. I can never remember this stuff.

But what about the rest?

The set of UI frameworks that I listed above are a tiny subset of the full list of UI frameworks that people build UI features with. So what about React, Android, iOS, XNA, etc? Well, they’re not discussed here.

But I will add a few points on web and UWP XAML.

The Web

I used to consider myself a jack of all trades and a master of none. Now, after many years working in the technical side of accessibility, I’ve revised how I view myself. I now consider myself a jack of some trades and a master of none. The more I work in accessibility, the more I realize how much I don’t know, and how much I depend on others to help me.

Way back I’d think how creative I was, sprinkling a little aria throughout my html, and improving my customer experience. I now realize that more often than not I’d be violating the rules on web accessibility with my changes, and sometimes doing more harm than good for my customers. But it seemed to fix whatever bug I was investigating at the time, so that’s ok. Or is it?

I now understand that I am not an expert in web accessibility, far from it in fact. Before I could ever feel confident enough to offer advice on aria, I’d need to spend an appropriate amount of time digesting the rules around web accessibility at the W3C site, and know the consequences of actions that I might recommend to web devs.

I have to say, Sarah Higley is the most knowledgeable web accessibility expert I’ve ever been lucky enough to learn from. If you really want to get yourself into a position where you can help others build accessible web UI, read and digest all Sarah's articles.

UWP XAML

UWP XAML is a powerful Windows desktop UI framework with respect to accessibility. I’m not going to discuss it here because I’d like to save that for a later article. And prior to that, I’m looking forward to experimenting with WinUI. At the time of writing this, WinUI 3.0 is in preview at Windows UI Library 3.0 Preview 1.

When it comes to Windows desktop apps which contain a mix of types of UI, I think Visual Studio has been the most interesting to me with its mix of WPF, WinForms, and Win32, (and I think I’ve seem web UI hosted in it too). So it’ll be very interesting to see XAML Islands thrown into the mix of what's available to desktop apps, and to see how much of a big happy UI family we end up with from an accessibility perspective.

And you can bet when I do that, I'll be paying attention to what the ever-helpful Accessibility Insights for Windows (AIWin) tool has to tell me about the UIA FrameworkId property. I often can't tell from a control's visuals whether it's Win32, WinForms, WPF, or UWP XAML, but the FrameworkId property lets me know that. For more information on the FrameworkId, visit the “Who is your UIA provider?” section of UI Automation: An Incomplete Guide for UI builders – Part 1.

One final thing about UWP XAML. Since it’s similar in some ways to WPF, the steps you take around making your UWP XAML more accessible, are somewhat related to those you take to making a WPF app accessible. The steps are not always the same, but some are close enough it’s worth you reading the WPF details below. And for a couple of critically important UWP XAML classes, visit AutomationProperties and AutomationPeer. And review my snippets in the UWP XAML section at Code samples for resolving common programmatic accessibility issues in Windows desktop apps.

And now, for the updated Win32, WinForms, and WPF details…


Win32

As far as I know, when it comes to common steps relating to Win32 accessibility, the details described at Common approaches for enhancing the programmatic accessibility of your Win32, WinForms and WPF apps: Part 2 – Win32 still apply. So read that.

Since I wrote that article, I created the related public Win32 snippets out at Code samples for resolving common programmatic accessibility issues in Windows desktop apps. So I thought I give an example of the consequences of applying some of that sample snippet code.

Say I have a control and I want to programmatically expose some supplemental helpful information on it. For this example, I choose to use the code snippet at Win32: Setting supplemental information on an control, and I’ll tweak it to have the information exposed through the UI Automation (UIA) HelpText property.

// 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 SetFullDescriptionProperty(HWND hDlg)

{

    HRESULT hr = S_OK;

 

    hr = CoCreateInstance(

        CLSID_AccPropServices,

        nullptr,

        CLSCTX_INPROC,

        IID_PPV_ARGS(&_pAccPropServices));

 

    if (SUCCEEDED(hr))

    {

        // Get the text from the label shown visually near the control.

        WCHAR szControlDescription[MAX_LOADSTRING];

        GetDlgItemText(

            hDlg,

            IDC_LABEL_CONTROLDESCRIPTION,

            szControlDescription,

            ARRAYSIZE(szControlDescription));

 

        // Set the UIA HelpText property on the control,

        // from the visual label text.

        hr = _pAccPropServices->SetHwndPropStr(

            GetDlgItem(hDlg, IDC_CONTROL),

            OBJID_CLIENT,

            CHILDID_SELF,

            HelpText_Property_GUID,

            szControlDescription);

    }

}

 

// Run when the UI is destroyed.

void ClearFullDescriptionProperty(HWND hDlg)

{

    if (_pAccPropServices != nullptr)

    {

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

        MSAAPROPID props[] = { HelpText_Property_GUID };

        _pAccPropServices->ClearHwndProps(

            GetDlgItem(hDlg, IDC_CONTROL),

            OBJID_CLIENT,

            CHILDID_SELF,

            props,

            ARRAYSIZE(props));

 

        _pAccPropServices->Release();

        _pAccPropServices = NULL;

    }

}

 


I pasted the above code into a new Win32 app built from the VS template for Win32 apps. I added a PUSHBUTTON to the Help dlg, with an accompanying visual LTEXT label, and called SetHwndPropStr() to have the button’s UIA HelpText property be the same as the visual label’s text.

Figure 1 below shows the AIWin tool reporting that the button’s UIA HelpText property is indeed set as expected.

The AIWin tool reporting that a button’s UIA HelpText property is the same as a label shown visually near the button. The button’s UIA Name property is “Buy”, and the HelpText property is “Any attempt to buy anything will be ignored. Sorry.”

Figure 1: The AIWin tool reporting that a Win32 button’s UIA HelpText property is the same as a label shown visually near the button. The button’s UIA Name property is “Buy”, and the HelpText property is “Any attempt to buy anything will be ignored. Sorry.”


Win32 Summary

I’ve pulled the following summary steps from my old Win32 article pretty much as-is, as I’d say they still sum things up nicely.

  • Where practical, replace custom UI with a standard Win32 control which is accessible by default. Perhaps the custom UI isn't really essential.
  • If some control has no accessible name, consider whether a visible or hidden label added immediately before the control could be used to provide the control with an accessible name.
  • Consider whether use of SetHwndProp() or SetHwndPropStr() could be used to customize specific UIA properties on a control.
  • If you already have an IAccessible implementation, consider whether support for targeted UIA patterns could be added through the use of IAccessibleEx. And if it’s the only way to help your customers, consider adding an IAccessible implementation to your UI, in order to add the IAccessibleEx functionality.
  • If you need to raise an event to make a screen reader aware of a change in your UI, consider calling NotifyWinEvent, passing in WinEvent ids or in some cases UIA event ids, as listed at Event Constants. (Or if you have a full native UIA automation, you could call UiaRaiseAutomationEvent() rather than NotifyWinEvent().)


WinForms

Y’know, now that I look at Common approaches for enhancing the programmatic accessibility of your Win32, WinForms and WPF apps: Part 3 – WinForms, I’d say that’s still worth reading. Most of what I said there is worth considering today, and in addition, subsequent accessibility enhancements can help you even further.

For details of how the accessibility of .NET Framework has been enhanced in recent versions, visit What's new in accessibility in the .NET Framework.

Important: Please, please, please consider the steps discussed at Accessibility switches. Even if you can’t for some business reasons rebuilt your app to target .NET Framework 4.8, you can still help your customers benefit from the accessibility enhancements available in 4.8 if 4.8 is available on the machine on which your app is running. This is possible through a small update to your app’s config file, and I’ve yet to encounter a team which felt it was not appropriate to make that change.

 

I’d say the two .NET enhancements of most interest to you as a WinForms developer, relate to Labels supporting LiveRegions, and support for raising the UIA Notification event.

LiveRegionChanged event

At the time of writing my old article, WinForms didn’t natively support raising the LiveRegionChanged event. So with a bucket-load of custom code, I invented my own “LiveLabel” which could raise the event. I described how I did that at Let your customers know of important status changes in your WinForms app.

Thankfully, my LiveLabel is no longer necessary, as for WinForms apps targeting .NET Framework 4.8, it’s quite straightforward to have the event raised when text changes on a Label. Thank you .NET!

Notification event

Similarly, at the time of writing my old article, WinForms didn’t natively support raising the UIA Notification event. So for this, I invented a “NotificationLabel”, and wrote a ton of code to enable it to raise the event. The code for that is up at Can your desktop app leverage the new UIA Notification event in order to have Narrator say exactly what your customers need?

And again, I’m very pleased to say that WinForms targeting .NET Framework 4.8 now makes it straightforward today to raise that Notification event. Thank you .NET!

Of course, to raise too many events can degrade your customer experience to a point where the app becomes unusable. So one should think long and hard about what events will result in the best possible experience for your customers.

As a test, I just copied the LiveRegion and Notification code snippets up at What's new in accessibility in .NET Framework 4.8 into a new WinForms app.

Note: At the time of writing this, the published snippet seems to refer to an “AutomationLiveSetting”, but that doesn’t seem to exist. So my modified code is as follows, and I also took the opportunity to use an Assertive LiveRegion, as I’ve used that more often that a Polite one.

labelNetworkStatus.LiveSetting =

    System.Windows.Forms.Automation.AutomationLiveSetting.Assertive;

And the following snippet is the code I ran to raise the UIA Notification event.

MethodInfo raiseMethod =

    typeof(AccessibleObject).GetMethod("RaiseAutomationNotification");

if (raiseMethod != null)

{

    // Be really sure you should be raising this event. Typically sighted

    // customers and customers who use screen readers are better

    // served by Labels that are LiveRegions.

    raiseMethod.Invoke(

        this.AccessibilityObject, // Typically some control raises the event.

        new object[3] {

            AutomationNotificationKind.Other, // You pick what's best.

            AutomationNotificationProcessing.All, // You pick what's best.

            "Hooray for .NET!" }); // Pass a localized string.

}


Figure 2 below shows the AIWin tool reporting the LiveRegionChanged and Notification events being raised as I interact with the app.

And goodness, the fact that it takes just one extra line of code to make screen readers aware of important changes to the text shown by Labels, has to be one of my favorite advances in recent years around making it practical for app devs to help their customers.

Note: What's new in accessibility in .NET Framework 4.8 into a new WinForms app also includes code for turning your custom controls into LiveRegions, so please do consider how that might also help your customers.

The AIWin tool reporting that the WinForms app raises a LiveRegionChanged event when a Label’s text changes to "The network's back! Oh dear, it's gone again.", and a UIA Notification event whose text payload is "Hooray for .NET!"

Figure 2: The AIWin tool reporting that the WinForms app raises a LiveRegionChanged event when a Label’s text changes to "The network's back! Oh dear, it's gone again.", and a UIA Notification event whose text payload is "Hooray for .NET!"


For some of my most recent musings on WinForms app visit Customizing the accessibility of a simple demonstration WinForms app and Real-world learnings on keyboard accessibility in WinForms and WPF apps: Part 1. And there are more code snippets out at the WinForms section at Code samples for resolving common programmatic accessibility issues in Windows desktop apps.


WinForms Summary

The following is an updated version of my original WinForms summary.

  • Where practical, replace custom UI with a standard WinForms control which is accessible by default. Perhaps the custom UI isn't really essential.
  • If a control has no accessible name, consider whether a label added immediately before the control could be used to provide the control with an accessible name.
  • If a control has no accessible name, consider setting the control's AccessibleName property.
  • Consider whether you can supply helpful supplemental information from a control using QueryAccessibilityHelp.
  • Consider whether overriding a property in a custom AccessibilityObject for the control could help your customers.
  • Consider whether you could use a Label or other control as a LiveRegion, or a Notification event, to make your customers aware of important status changes in your UI.
  • If you need to raise other events to make a screen reader aware of a change in your UI, consider either calling AccessibilityNotifyClients from derived controls, or use interop to call NotifyWinEvent.


WPF

Yet again, I’d recommend you read my original Common approaches for enhancing the programmatic accessibility of your Win32, WinForms and WPF apps: Part 4 – WPF, as it seems full of stuff that’s still useful today.

Important: It also includes a handy snippet on how to make your DataGrid’s mouse-sortable columns also sortable when your customers are only using the keyboard. Please make sure all functionality accessible via mouse input is also efficiently accessible using only the keyboard.


As with WinForms apps, thanks to the accessibility enhancements available in recent versions of .NET Framework, WPF app builders are more empowered to enable their customers. The WPF section at What's new in accessibility in .NET Framework 4.8 lists a few things that I find particularly interesting. For example, new support for the UIA SizeOfSet, PositionInSet, and ControllerFor UIA properties. And while that support will be very important in some scenarios, I’ll not discuss them here, as this article relates to steps you may take to address common accessibility bugs. Typically devs building UI with standard WPF controls won’t have to take explicit action relating to those UIA properties.

Having said that, I really must call out one particular .NET Framework enhancement which I am rather fond of…

In earlier versions of .NET, if a control’s Visibility were set to something other than Visible, it would be exposed through the Control view of the UIA tree, and have a UIA OffScreen property of true. It might be said that technically, that programmatic representation is appropriate. But in practice, screen reader’s like Narrator could still announce the control, and that could lead to a very confusing experience. In .NET Framework 4.8, such controls are not exposed through the Control view of the UIA tree, and that makes it quite clear to screen readers that the controls are not of interest to your customers while the controls are not Visible. Thank you .NET!

And that last point means that if you previously took explicit action in your code to remove some not-Visible controls from your customer experience, perhaps you no longer need to take that action.

As with WinForms, perhaps the most important accessibility enhancement in .NET Framework for you as a WPF app dev, relates to making your customers aware of important status changes that occur in your app. As I said in my original WPF article, it was a lot of work at the time for an app dev to add support for LiveRegions to a WPF app. What I really meant was that it was technically possible, but almost no-one would ever invest in all that work. And I wouldn’t expect any app dev to do so.

The great news is that WPF apps have had native support for LiveRegions since .NET Framework 4.7.1 was released. I have a related code snippet at WPF: A screen reader isn't announcing an update to a status string or error string.

Important: As I say as that resource, “Always raise the event after the updated text has been set on the TextBlock.”


At the time of writing this, (when .NET Framework 4.8’s available,) WPF doesn’t natively support raising UIA Notification events. Typically that’s not an issue when addressing common accessibility bugs, as use of LiveRegions alone can often be sufficient for helping you ensure that your customers are made aware of important status updates.

Note: A while back I did invent a WPF “NotificationTextBlock” which would enable you to have your WPF app raise a UIA Notification event if that’s really the best way of serving your customers. I’ll not discuss that in detail here, because that sort of action is typically not required for fixing common accessibility bugs. But should you be interested, I shared the code for the NotificationTextBlock in the WPF section at Can your desktop app leverage the new UIA Notification event in order to have Narrator say exactly what your customers need?

Important: I’m really not kidding about the critical importance of making sure your customers are well aware of changes occurring in your UI. When I wrote The accessibility of Sight Sign – an eye-gaze controlled app for writing a signature with a robot, I described how I worked to address the accessibility of the app in a number of ways, and then said: “Why the above changes alone leave the app woefully inaccessible.”


If customers are not made aware of important time-sensitive information, then they may be blocked from completing their tasks. So don’t only consider snapshots in time of your programmatic interface, but also the real-time customer experience as your UI changes.

I added the code snippet below in the Click handler of a button in a test WPF app, and sure enough, I then found the AIWin tool reported that that LiveRegionChanged event was raised as expected.

<!-- In the XAML. ->

<TextBlock x:Name="StatusTextBlock"

    AutomationProperties.LiveSetting="Assertive" />

 

 

// In the code-behind when the text changes on the TextBlock.

 

// Todo: Localize this text.

StatusTextBlock.Text = "Warning: All your data may have become unreliable.";

 

var peer = FrameworkElementAutomationPeer.FromElement(StatusTextBlock);

if (peer != null)

{

    peer.RaiseAutomationEvent(AutomationEvents.LiveRegionChanged);

}


Note: While some terminology related to the above includes the phrase “LiveSetting”, I still refer to any UI that raises a LiveRegionChanged event as being a “live region”.

The AIWin tool reporting that a LiveRegionChanged event was raised by a TextBlock who’s text is “Warning: All your data may have become unreliable.”

Figure 3: The AIWin tool reporting that a LiveRegionChanged event was raised by a WPF TextBlock whose text is “Warning: All your data may have become unreliable.”


For more code snippets related to fixing common accessibility bugs, visit the WPF section at Code samples for resolving common programmatic accessibility issues in Windows desktop apps.

And as with WinForms, please, please, please consider whether the steps described at Accessibility switches could help many of your customers have a more empowering experience at your WPF app.


WPF Summary

The following is an updated version of my original WPF summary.

  • Where practical, replace custom UI with a standard WPF control which is accessible by default. Perhaps the custom UI isn't really essential.
  • Consider whether the AutomationProperties class can provide essential information like a UIA Name for a button which shows no text visually, or provide supplemental information like UIA HelpText or ItemStatus.
  • Consider whether a custom AutomationPeer can be used to add support for specific UIA patterns to your UI, or to set useful UIA properties not accessible through the AutomationProperties class.
  • Examine the UIA representation of your list- and grid-related UI, and consider if style property setters might help make that UI more accessible.
  • Consider whether you could make a TextBlock or other control a LiveRegion, to make your customer aware of important status changes in your UI. If you need to raise other events to help your customers, consider raising other AutomationEvents or calling RaisePropertyChangedEvent().


Summary

The goal of this article is to introduce many of the steps relevant to fixing common programmatic accessibility bugs in Win32, WinForms, and WPF apps. There are many more technical details relating to the accessibility of such apps, but if you take advantage of the referenced support that’s provided by the various UI frameworks, you’ll be well on the way to delivering the experiences that your customers need in order for them to successfully complete their tasks with your apps.

And I have to say, it means a lot to me to review my old Common approaches for enhancing the programmatic accessibility of your Win32, WinForms and WPF apps series, and find that two of my three custom control inventions serve no purpose at all now for apps that can target .NET Framework 4.8.

That’s exactly what I want to have happen, as while the industry has so very far to go before anyone can say all customers are being served well, it seems we’re heading in the right direction.

Guy

To view or add a comment, sign in

More articles by Guy Barker

Others also viewed

Explore content categories