Customizing the accessibility of a simple demonstration WinForms app
This article discusses some important steps relating to enhancing the accessibility of a simple demonstration WinForms app, and compares the results with those of a WPF app which contains the same features. The code for both apps is publicly viewable.
Introduction
When I watch a film, I don’t tend to sit there wondering what equipment was used to create the film. Rather I enjoy of experience of consuming the end product. (Or not, depending on the film.)
Similarly, when your customers use your app, they’ll not tend to think, “Wow! This is such a helpful app! I wonder if it was built using Win32, WinForms, WPF or UWP XAML.” In fact they probably won’t care about the UI framework used at all, and nor should they have to. Rather your customers will concentrate on enjoying the experience that came about as a result of all the work you put into building your app’s inclusive design.
So after I built the simple WPF demo app described at Real-world lessons on building accessible WPF apps recently, I wondered about how I might deliver exactly the same experience with a WinForms app. I wouldn’t want some customers of the app to have to pick a particular version because the other version is less usable to them. If an app is noticeably less usable due to the type of UI used to build it, what would that mean to customers of serious business apps? Could customers be blocked from using a serious business app simply because of the UI framework that a product team happened to choose for building the app? Let’s hope not.
This article describes the steps I took to have the WinForms demo app deliver the same, (or at least very similar,) experience to that of the WPF app. For me, the most interesting topics here relate to making text bigger, showing high contrast visuals, and customizing the programmatic interface of the app. The app is not intended to show best practices around building WinForms apps in general.
And I have to say, I was pretty chuffed by how similar the experiences were in the WPF and WinForms apps by the end of the experiment!
The code for the WinForms and WPF apps is all viewable at GFClock.
I’d strongly recommend you read Real-world lessons on building accessible WPF apps before reading this article, even if you don’t build apps with WPF. Some of the discussion in that article is not UI-specific, and I won’t be duplicating it in this article.
Figure 1: The WinForms and WPF versions of the app, running side-by-side. Both apps look very similar. With images of a grandfather clock face with hands showing the current time, and text beneath the clock face showing “14:43”.
What’s not covered in this article?
In my earlier article, I specifically called out some very important topics that were not included in my WPF experiment. These topics are also not included in this new WinForms experiment, and are listed below.
- Inclusive design: Yup, it’s still not inclusive.
- Keyboard accessibility: Yup, there’s still no keyboard focusable elements in the UI.
- Resizing the app: Yup, there’s still lots of hard-coded size and location values.
I am looking forward to updating the apps at some point to address all these constraints, but who knows when.
So what important stuff is covered in this article?
It’s interesting for me here to list the topics to be demonstrated using this WinForms app, in exactly the same way as I listed them for my WPF app.
- Ask first, can I avoid bugs simply by leveraging the latest version of .NET?
- Text scaling.
- High Contrast Themes.
- Moving the app with the keyboard.
- Programmatic Accessibility Part 1: Providing the data your customers need.
- Programmatic Accessibility Part 2: Including what matters to your customer.
- Programmatic Accessibility Part 3: Raising events when things change.
And here we go…
Ask first, can I avoid bugs simply by leveraging the latest version of .NET?
Always have your WinForms app leverage the most recent version of .NET that you can. Over the last few years, with each new release of .NET, there have been some great improvements around accessibility. To learn more about that, visit What's new in accessibility in the .NET Framework. And to learn how you can leverage the improvements available in the latest version of .NET Framework, without having to build for that version, visit Accessibility switches. This is just so helpful to product teams!
Whenever I get asked about a WinForms app’s accessibility, the first thing I say is, what version of .NET Framework is being used?
Text scaling
This is where things started to get really interesting. There are a few ways that customers using Windows 10 can influence the size of stuff showing on the screen. For example, there’s the “Make everything bigger” Ease of Access setting, the “Display resolution” Display Setting, and the Windows Magnifier feature. A WinForms app will respect all those things by default. Goodo.
But what about the “Make text bigger” Ease of Access setting? Today the size of text shown in controls in WinForms apps doesn’t change with that setting by default. So if my WinForms app is to behave in the same way as my WPF app, I’ll have to take some action myself here.
For your app to access your customer’s choice of “Make text bigger” setting, it needs to get at the UISettings.TextScaleFactor. As far as I know, all the official documentation about that property relates to UWP XAML apps, and I’ve never been too sure as to how a Win32 or WinForms app can get hold of the it. When I queried this the other day, someone responded by writing some very helpful sample code showing how a Win32 app can get the value. The code would need a little tweaking before shipping in a professional app, (for example, to react to a change in the “Make text bigger” setting while the app’s running,) but it was absolutely perfect as-is for my demo purposes!
So I took the sample code and built a Win32 dll exporting a function which got the text scale factor, and used a little interop to enable my WinForms app to access it. With that text scale factor, I can now resize the text font for any text shown in the app. At the moment, that simply means the one Label control in the app.
Now that the size of text shown in the app can change, it’s critically important to consider whether the text can always fit in the space available to it. Maybe in some situations, having text truncated when it can’t fit in its container provides the most helpful experience for an app’s customers, and that may be practical by using a Label’s AutoEllipsis property. But in the case of my simple app, I always need the full text on the Label to be visible, and so I’ll achieve this by using my Label’s AutoSize property.
And what’s more, by also setting AutoSize true on the app’s main Form, the app window can grow to accommodate the size of the Label, as the Label grows to fit the text within it. That’s all rather handy indeed.
Figure 2: The app showing the text “It’s one o’clock and time for lunch”, with the “Make text bigger” setting at 100%.
Figure 3: The app showing the text “It’s one o’clock and time for lu” followed by an ellipsis, with the “Make text bigger” setting at 200%.
Figure 4: The app showing the text “It’s one o’clock and time for” on the first line in the Label, and “lunch” on the next line, with the “Make text bigger” setting at 200%.
High Contrast Themes
When it comes to WinForms-specific resources relating to supporting Windows High Contrast, you may be interested in the following:
- Supporting High Contrast Mode. This has very helpful code snippets which I copy/pasted into my app. And interestingly, the event handler I copied in for reacting to changes in state of high contrast, not only gets called when high contrast is turned on or off, but also when switching between high contrast themes.
- SystemInformation.HighContrast Property. The property to check when determining if a high contrast theme is active.
- SystemColors Class. This can be very helpful to designers considering what colors should be shown on custom UI when a high contrast theme is active. For example, say you want something to be shown using the same color as that of text shown on buttons, whatever that color happens to be. Then use ControlText.
With the code snippets I’d copied in, there were really only two things I needed to do that were specific to my app. The first was to make sure when a high contrast theme is active, I show the Label’s text and background using the SystemColors.WindowText and SystemColors.Window colors respectively. I don’t care what those colors actually are, but I can feel confident that the text will be high contrast against its background, and they’re the colors that my customers want.
The second thing to do is show high contrast images for the clock face and hands, and make sure I show the appropriate light-on-dark or dark-on-light versions of the images depending on the nature of the high contrast theme that’s active.
Figure 5: The app showing light-on-dark visuals for the text and clock images.
Figure 6: The app showing dark-on-light visuals for the text and clock images.
For some of my own thoughts on the expectations around the use of high contrast themes in Windows, visit Respecting your customers’ choice of high contrast colors on Windows.
Moving the app with the keyboard
The app must support my customers who want to move the app around the desktop, and minimize it, using only the keyboard, and in a way that’s intuitive for my customers. WinForms provides this by default, through the standard window menu which appears in response to a press of Alt+Space. Goodo.
Figure 7: The app with its standard window menu open, and the “Move” item highlighted in the menu.
Programmatic Accessibility Part 1: Providing the data your customers need
Note that while developing the programmatic interface for my app, I rely heavily on the Accessibility Insights for Windows (AIWin) tool to help to learn about the interface that my app’s delivering.
Programmatic accessibility is another area which I do feel is really interesting. If all my customers are to get the same experience at both my WinForms app and my WPF app, then this includes all my customers who use screen readers. So is it possible for me to deliver the same programmatic representation through the UI Automation (UIA) API for my WinForms app as I did for my WPF app?
To help me influence a number of UIA properties associated with my clock’s UserControl, I created a custom AccessibleObject. With this, I could then override the following three properties:
1.Role
This influences the UIA ControlType property. One challenge here is that there’s not a one-to-one relationship between the values available through the WinForms Role property and the UIA ControlType property. There’s some overlap where the WinForms Role maps exactly as you’d expect to the UIA ControlType. For example, a WinForms Role of PushButton maps to the UIA ControlType of Button. But what about all the other values?
As it happens, WinForms does have a role of Clock, which really seemed quite tempting, given that semantically, my UserControl is a clock. However, when I tried giving my UserControl a role of Clock, the associated UIA element ended up with a ControlType of Button. That’s not what I want at all, given that it sets all the wrong expectations to my customers and to screen readers, as to exactly what can be done with the clock. So I instead picked a role of Graphic, which gets mapped to a UIA ControlType of Image. That seemed the best available option, given that the UserControl is showing an image of a clock.
2. Name
This will become the UIA Name property. Given that I can’t set a UIA ControlType of Clock, I set the Name to “Grandfather Clock”.
3. Value
This is where the mapping from WinForms to UIA worked great for me. By overriding the WinForms Value property to be the current time shown on the clock, this led to the UIA Value pattern automatically being supported on my UserControl, and the WinForms’s Value became the UIA Value pattern’s Value property. Smashing!
The two images below show “Before” and “After” results with respect to the use of my custom AccessibleObject. In the “Before” case, the clock was exposed as a nameless pane with no value. In the “After” case, the clock is exposed as an image named “Grandfather Clock”, whose value is the current time.
Figure 8: The AIWin tool reporting the custom clock control in the app is being exposed through UIA as having no Name, and a LocalizedControlType of “pane”.
Figure 9: The AIWin tool reporting the custom clock control in the app is being exposed through UIA as having a Name of “Grandfather Clock”, and a LocalizedControlType of “image”.
Programmatic Accessibility Part 2: Including what matters to your customer
This topic relates to the goal of only exposing things through UIA if those things actually matter in practice to your customers. The idea being that your customers don’t want to be distracted by elements that serve no useful purpose to them.
When I first started working on the WinForms Clock app, I had the clock’s UserControl host three PictureBoxes. The PictureBoxes presented the images of the clock face and the two clock hands. When I did that, each PictureBox would get represented through UIA as its own element.
Note: By default the UIA ControlType of the element representing the PictureBox is Pane. Often your customer would actually need that ControlType to be Image instead, given that semantically, Image is more likely to be a better match. I describe a quick ‘n’ easy way to get the UIA ControlType exposed as Image instead of Pane, at WinForms: Conveying a PictureBox programmatically as being an image.
In the case of my demo WinForms app, having separate UIA elements for each of the three images is not helpful to my customers, as everything my customers need to know is accessible directly through the clock’s UserControl. In the case of WinForms, it seems that it’s not straightforward to remove these image-related elements from the UIA tree. It seemed tempting to override my custom AccessibleObject’s GetChildCount and have it return zero, but that didn’t prevent the Image elements getting exposed through UIA. Overriding anything else in the AccessibleObject also didn’t seem to prevent them being exposed.
This would leave two options. The first being to implement whatever UIA hierarchy-related interfaces would be required to really generate the UIA element hierarchy that is most helpful to my customers. This would include implementing such things as IRawElementProviderFragment.Navigate. And as much fun as it would be to do that, I can think of more fun ways to spend a Saturday afternoon, so I’ll not be doing that today.
The other option would be to leave the nameless Pane elements as they are, and if my customers were to encounter the elements, they’ll just have to learn to ignore them. As it happens, the Narrator screen reader won’t navigate to the elements anyway today, given that a nameless pane is typically unlikely to be of interest to customers.
And that all said, once I started working seriously on the app, I didn’t use PictureBoxes anyway. To render the clock, I simply painted the clock face and hands myself, and rendering stuff like this won’t result in any UIA elements being created. So I got the target UIA hierarchy that I was after by default.
Important: Please do remember that when you’re custom rendering something in a WinForms app, if some part of those visuals should be exposed through UIA as its own element, you’ll need to take specific action to make that happen.
Figure 10: An early app implementation with three PictureBoxes being exposed through UIA as nameless pane elements.
Figure 11: A later implementation of the app where custom rendering of the clock visuals led to no unwanted UIA elements being exposed.
Programmatic Accessibility Part 3: Raising events when things change
In my WPF app, I’d explicitly taken action to raise two UIA events.
One of those events was a LiveRegionChanged event, and since .NET Framework 4.8, WinForms has made it really straightforward to raise that event. Typically the LiveRegionChanged event would be raised by a Label whose text has just changed to reflect some new information, but I thought it’d be interesting to raise the event directly off the clock’s UserControl if I could. That control’s UIA Name property isn’t changing, but it’s Value is.
Note that in the case of a Label whose LiveSetting propety has been set Assertive or Polite, WinForms itself will automatically raise the LiveRegionChanged event when the text changes, which is really handy.
So I updated my UserControl to support IAutomationLiveRegion, and had the UserControl declare itself to be an Assertive LiveRegion. And then I called RaiseLiveRegionChanged after the new value was available from the control. Sure enough, in response to the event being raised, Narrator announced the updated value. Great!
One thing I was expecting however, is that when the AIWin tool reports the details of the element which raised the event, it shows the element as being a nameless Pane. So it seems that the custom ControlType and Name that I’d set on the UserControl aren’t being accessed through the event details. I don’t know why that is, but at least the change was being announced, which was my goal here.
Figure 12: The AIWin tool reporting a LiveRegionChanged event being raised by the app.
The other event I need to raise in the app helps screen readers know when the value on the clock is changing. This means that one way or another, I need to get a property changed event raised for the UIA Value pattern’s Value property. Given that I’m not implementing the Value pattern myself, I wasn’t sure how to get the event raised. I first tried calling AccessibilityNotifyClients with AccessibleEvents.ValueChange, but that didn’t seem to work. So I then called NotifyWinEvent through interop with UIA_ValueValuePropertyId, and that didn’t work either.
In the end I called NotifyWinEvent through interop, passing in EVENT_OBJECT_VALUECHANGE, and that seemed to work fine. The figure below shows the AIWin tool reporting the UIA Value pattern’s Value property change event is being raised as required.
Figure 13: The AIWin tool reporting a property changed event being raised for the Value pattern’s Value property supported by the clock control.
So how similar are the WinForms and WPF apps?
With the changes I described above in place, it’s interesting to consider just how similar my WinForms and WPF apps are. The figures below show the WinForms app and the WPF app both showing large text based on an Ease of Access “Make text bigger” setting of 150%, and the High Contrast Black theme active. They also show the AIWin tool reporting the UIA hierarchy of the apps.
Figure 14: The WinForms version of the app, shown with the “Make text bigger” setting at 150%, and the High Contrast Black theme active. The AIWin tool is reporting the UIA representation of the app.
Figure 15: The WPF version of the app, shown with the “Make text bigger” setting at 150%, and the High Contrast Black theme active. The AIWin tool is reporting the UIA representation of the app.
Overall, I’m pretty pleased with how similar the apps are. And I doubt that whatever differences that do exist between them would have a significant impact on my customers’ experiences should they happen to use both versions.
Summary
WinForms does provide a really simple way to customize the UIA Name and ControlType properties for a custom control, through use of the AccessibleName and AccessibleRole. I could have used those in my demo app, but in fact I didn’t, because I needed a custom AccessibleObject in order to get the value of the clock exposed through UIA. Given that I needed that AccessibleObject, I decided to use it to override all of its Value, Name, and Role.
Note: Don’t bother trying to use the AccessibleDescription off the control, as that won’t get exposed through UIA in the way you’d expect. Instead, to set some supplemental helpful information off the control, follow the steps I described at WinForms: Setting helpful supplemental information on a control.
To complete the UIA-based experience here, I made sure to raise the expected UIA events through NotifyWinEvent via interop, (though presumably in some cases AccessibilityNotifyClients works fine,) and RaiseLiveRegionChanged.
The steps to respect whatever high contrast theme is active, were really quite straightforward, and being able to choose colors based on the long list of SystemColors properties means I can feel confident that all my UI can be shown with the contrast that my customers expect.
The steps to have my app’s Label respect the Ease of Access “Make text bigger” setting weren’t trivial, but they seemed to generate the results I was after. And the handy AutoEllipsis and AutoSize properties could help deliver the best experience based on the specific content being shown in the Label.
I’m now looking forward to enhancing the WinForms and WPF demo apps, and removing some of their constraints. For example, to add support for app resizing, and adding more controls so that I can consider a great keyboard experience.
I’ll be back in touch when I’ve made progress on that. I can’t do it now, as I really can’t put the weeding off any longer…
Guy
Hot off the press! I’ve updated the WinForms and WPF apps to support being resized. So customers can now do Alt+Space, ‘S’, and then move the arrow keys around to resize the app windows. Followed by a press of Enter to accept the change, or Cancel to undo it. Pictures of the apps being resized are available at https://twitter.com/gbarkerZ/status/1252641041548578816?s=20. So try resizing and moving windows yourselves using only the keyboard. Hours of fun to be had. Lots of apps support it, including some that don’t show anything to indicate that a window menu’s available. For example, Edge and the Win10 Calculator app. Just press Alt+Space at the app and see what happens! The changes I made to my demo WinForms and WPF are absolutely not aimed at showing best practices for resizing app visuals. For example, the WPF app has some very hard-coded values associated with positioning and scaling the clock hands, but they work well enough for now. Also, it’s worth noting that I’ve not done the work to support the app being tiny. Both apps support the Windows 10 “Make text bigger” setting, and the containing control grows in height to fit the text, so that’s good. But what happens when the height of the text control becomes taller than the app window? I’ve not added the necessary vertical scroll bar for that scenario. So lot’s of things to experiment with at some point, but this latest change is sufficient to encourage everyone to get familiar with resizing apps via the keyboard. My next update to the apps will be to add UI which hosts a bunch of controls, in order to discuss tab order and programmatic order. Do you know what the programmatic order of the controls in your app is? Your customers are depending on you to know it.