Real-world learnings on keyboard accessibility in WinForms and WPF apps: Part 1
The Settings windows in the demo WinForms and WPF GFClock apps, and the AIWin tool reporting the programmatic interface of the latter.

Real-world learnings on keyboard accessibility in WinForms and WPF apps: Part 1

This article discusses some real-world bugs relating to the keyboard accessibility of apps built using WinForms and WPF UI. Please consider whether your customers are blocked from completing their tasks because of similar bugs in your apps.

The demo apps mentioned in this article do not demonstrate best practices for building WinForms and WPF apps, but rather are used only to show options around implementing certain accessibility-related functionality. The code demonstrating the topics discussed in this article is at GFClock.


Introduction

I recently built two simple WPF and WinForms apps with the goal of sharing some learnings on text scaling, high contrast support, and programmatic access. All the related discussions are at:


I called out in those articles that some very important topics were not included in the discussions, and one of those topics was keyboard accessibility. So in this article I’m going to discuss a number of keyboard-related questions that I’ve been involved with in recent weeks, where the questions have been raised following bugs being logged against shipping products. So these topics are not theoretical, but rather ones which have led to questions around real apps’ user experiences.

I’m calling this article a “Part 1”, because some of the topics discussed here relate to pretty much any app that has UI. This is not about keyboard interaction with custom controls, but rather keyboard interaction using standard WinForms and WPF controls. Perhaps you may already be familiar with much of what I say in this article, but I do feel it’s important that I say it, because I know through my recent experiences that for each point I make, the points are not universally known by app devs.

And I’ll bet there’s something amongst all this that you weren’t aware of. ??

Maybe I’ll do a “Part 2” relating to the keyboard experience for custom controls if I learn of questions being raised about that.

The specific topics that I’ll be discussing here are:

  • Order, Order! The keyboard and programmatic order of your controls will make or break the experience for your customers.
  • Where’s keyboard focus?
  • Efficiently triggering action.
  • Can two UI frameworks ever provide the same experience for your customers?
  • 1001 things you need to know about “data grids”.


I’ve updated my demo apps up at GFClock to include a Settings window, to help me discuss the topics in this article. Please do let me know if there’s anything in particular that you’d like me to add to the apps as part of demo’ing some accessibility-related topic.


The WinForms and WPF apps showing their respective Settings windows.

Figure 1: The WinForms and WPF apps showing their respective Settings windows.


Before we get started

Before we get into the details of the topics I listed above, there are a few things I’d like to mention first.


Always leverage the accessibility improvements in .NET Framework 4.8

I know I said this in other articles, but it’s just so very important and not everyone knows it yet. Over recent years, there have been many accessibility improvements made to the .NET Framework. So by default, please leverage those accessibility improvements in the latest version of .NET.

The latest version of .NET Framework is 4.8, and for my demo WinForms and WPF apps, I built them to specifically target that version. (Discussion around .NET Core is out of scope for this article.)

So please consider leveraging the accessibility improvements in .NET Framework 4.8 in order to deliver the most accessible experience possible to your customers. You don’t have to rebuild your app to specifically target 4.8 if that’s not in-line with your business needs, but you can still leverage the fixes in 4.8 if 4.8’s available on the device where your app’s running, even if you’ve not rebuilt to target 4.8. This is such a helpful thing to do for your customers, please do consider doing this. The steps on how to achieve this are described at Access Switches.


Making the WinForms app DPI aware

When I added the Settings window to my WinForms app, the text in the window scaled by default as the Ease of Access “Make everything bigger” setting changed, but the text was a little fuzzy. So I declared the app to be DPI aware by adding an app manifest and updating it accordingly. As a result, the text in the Settings window looked super-crisp. Goodo.

For more information on declaring WinForms apps to be DPI aware, visit High DPI support in Windows Forms. (WPF apps are system DPI aware by default.)

Declaring the app to be DPI aware messed up my rendering of the hands on the face of the clock in the apps. So I needed to access the current “Make everything bigger” scaling value, and incorporate that somewhere in my various scaling, translating, and rotating of the clock hands. As part of that, I used interop to call MonitorFromWindow and GetDpiForMonitor, and ended up with a scaling value. It all seemed to work fine, and so the clock could once again tell the right time, and my Settings window’s text looked just marvelous.

Note that I didn’t do work manually to apply the current Ease of Access “Make text bigger” scaling to the control in my WinForms Settings window. WinForms apps don’t respect that setting today, but I’d already taken action to access that scaling in earlier experiments. Maybe one day I’ll experiment with applying that scaling to the Settings window, but not in this article I won’t.


One-handed clocks

When my dad and I visited Rufford Old Hall in Lancashire a while back, we came across a room that had a one-handed grandfather clock. I’d not seen one before, but apparently they were common in the 17th century. So whilst adding some radio buttons to the Settings dialog, I used them to control whether one or two hands would be shown on the clock. I only did this to remind myself of the trip to Rufford Old Hall, and it made me feel good. That’s all rather tangential to the topic of how controls from different UI frameworks might provide a consistent UX, but there we go.


The clocks in the WinForms and WPF apps showing only an hour hand on the clock face.

Figure 2: The clocks in the WinForms and WPF apps showing only an hour hand on the clock face.


To Business!

Ok, here are the details relating to the recent learnings coming from some real-world bugs.


Order, Order! The keyboard and programmatic order of your controls will make or break the experience for your customers

App builders typically lay out controls in UI visually in a way that matches a logical flow based on the purpose of the UI. Rarely would an app builder think to themselves that all that matters is that a window contains all the necessary controls, and where the controls are shown visually really doesn’t matter. In fact, I’d imagine most app builders would feel to deliver such a poor experience would be downright disrespectful to the app’s customers.


Two versions of the Settings window. The first shows controls laid out visually in a logical order which matches the purpose of the UI. The second shows the same controls laid out in an apparently random order.

Figure 3: Two versions of the Settings window. The first shows controls laid out visually in a logical order which matches the purpose of the UI. The second shows the same controls laid out in an apparently random order.


Now some people might feel I’m being facetious here, as few app developers wouldn’t care about the visual order of the controls in their UI. But a high-quality app that serves all its customers well, delivers its controls in a logical order regardless of how customers interact with those controls. So all of the visual order, the keyboard focus order, and the programmatic order, would be a great match for the logical order based on the purpose of the UI.

In the case of the keyboard focus order, traditionally pressing the Tab key will move keyboard focus from one control to the next, and Shift+Tab would reverse that navigation. And when keyboard focus is within a set of items, the Arrow keys would move between items in that set.

Customers who interact with your app using the keyboard will by default expect to be able to tab through the control in the same logical order that matches the visuals. But it is absolutely practical for app builders to build UI where customers will not experience this, and so it is a critical consideration for you as an app builder.

For example, by dragging and dropping controls into your WinForms app, the default keyboard order is based on the order in which you added the controls to the form rather than some visual order in the UI. The image below shows the Accessibility Insights for Windows (AIWin) tool recording the order in which keyboard focus moves through the controls in the Settings window, as the Tab key is pressed. The order does not match the visual order of the controls.


AIWin reporting that the Tab order in the Settings app does not match the visual order.

Figure 4: AIWin reporting that the Tab order in the Settings app does not match the visual order.


One way to resolve this issue is to use the Tab Order feature in Visual Studio, which provides a way to explicitly define the Tab order in the UI. This results in each control having an explicit TabIndex value being set. The image below shows the Tab order being explicitly set in Visual Studio, and that would result in a run-time Tab order that matches the visual order in the UI.


Setting the keyboard focus order in the Settings window in Visual Studio. The keyboard focus order is conveyed by each control having a number shown next to it, representing the control’s position in the overall Tab order.

Figure 5: Setting the keyboard focus order in the Settings window in Visual Studio. The keyboard focus order is conveyed by each control having a number shown next to it, representing the control’s position in the overall Tab order.


While ensuring a great Tab order for your customers is necessary, that alone is not sufficient to ensure a great experience for all your customers. Another consideration is the order of the elements as exposed through the UI Automation (UIA) API. A customer using the Narrator screen reader may choose to navigate through the UI using a navigation path that’s based on the programmatic order of the UIA elements which represent the controls. The image below shows AIWin reporting that the order of the elements exposed through UIA does not match the visual order, despite the Tab order being a match for those visuals.


AIWin reporting that the order of the elements in the UIA tree is not a match for the visual order of the controls in the Settings window.

Figure 6: AIWin reporting that the order of the elements in the UIA tree is not a match for the visual order of the controls in the Settings window.


One solution for this programmatic ordering issue is to rearrange the order in which the controls are added to their container, (whether the container is a GroupBox or Form,) and to ensure the controls are added in an order that matches the logical order based on the purpose of the controls.

I just did this with a few small tweaks in my Setting window’s Designer.cs file. What’s more, once the order of the adding of the controls to their containers matched the logical order, I no longer needed any explicit setting of the TabIndex values on the controls. Rather, the Tab order and the UIA order both matched the logical order as required.


AIWin reporting that the order of the UIA elements in the UIA tree matches their logical order.

Figure 7: AIWin reporting that the order of the UIA elements in the UIA tree matches their logical order.


AIWin reporting that the Tab order of the controls matches their logical order.

Figure 8: AIWin reporting that the Tab order of the controls matches their logical order.


Important: The impact of an incorrect order of controls in UI is not only related to the efficiency with which that UI can be used, (even though efficiency is very important). I’ve encountered apps where the incorrect order led to screen readers announcing the wrong labels for edit controls. So a customer would tab through a set of edit controls, and be told the wrong purpose of the edit controls as they encountered them, despite the appropriate labels appearing next to the controls visually. For a customer to be told that they’re working in some particular edit control, and they’re not, is a very serious issue indeed. Order matters.

Important: This isn’t only about WinForms. I’ve seen a WPF app which made heavy use of margins to deliberately position controls at some x,y position in the UI. So a visual layout had been achieved which had no relationship to the order in which the controls were defined in XAML. This led to a very poor experience for customers as they tabbed around the UI. Don’t use margins to push some control over to a particular point in the UI. Instead, define all your UI in a logical order in the XAML, and take advantage of XAML’s great support for layout management.

And a couple of other considerations regarding WPF apps, in addition to not using margins to explicitly position UI at some specific x,y position:

  • Be aware that if grid layouts are used to position UI, then again the order of the elements in the UIA tree may not match the logical order of the UI.
  • UI that can expand to reveal more content may be defined in such a way that the container for the expanding content is not defined next to the XAML element that expands. This means that once the UI is expanded, the expanded content is not next to the expanding element in the UIA tree. As such, when using Narrator's Scan mode, Narrator will move from the expanding element to whatever follows that element in the UIA tree, and not to the expanded content that appears next to the expanding UI visually.


Big Quiz Intermission!

The image below shows what AIWin reports about the Tab order as I navigate forward with the keyboard in the WPF Settings window. The numbers 1 through 5 seem to appear as expected by the controls, in a logical order that matches the visual order.

However, there’s no number showing for a Tab stop that I encountered between the controls that are marked as numbers 2 and 3. I had to press Tab twice to move from 2 to 3, and AIWin’s results don’t reflect that. Out of curiosity, I repeated my Tab test with Narrator running, and when I tabbed to the mysterious something-between-2-and-3, sure enough, Narrator made no announcement at all, which would be confusing for customers.

So the quiz question is: what’s going on?

I’ve not looked into this closely, but I suspect the answer lies with the UIA representation of a part of the DataGrid’s header. When I tab from the ComboBox lying before the DataGrid, keyboard focus feedback is shown across the entire header row, but neither AIWin nor Narrator move to the UI that’s showing that feedback.

Examining the DataGrid’s UIA elements in AIWin, I find only one element that seems to have a bounding rect that matches what gets highlighted with keyboard focus feedback, and that has a UIA AutomationId of PART_ColumnHeadersPresenter. So I’m going to assume that that’s the UIA element of interest. And AIWin informs me that while the UI shows the keyboard focus feedback, the element’s UIA IsKeyboardFocusable and HasKeyboardFocus properties are both false. So as far as AIWin and Narrator are concerned, the element doesn’t have keyboard focus. Hence neither of those products move to it when I press Tab whilst keyboard focus is at the control lying before it.

This is a very important point, and it’s something that I’ve seen multiple apps trip up on.

If you’re developing actionable custom controls, and you need to explicitly add support for keyboard accessibility, then by default, always consider the following:

  • Include the control in the Tab order, (or support arrowing to it, if it’s in a set of items).
  • Show clear keyboard focus feedback when it has keyboard focus.
  • Set its UIA IsKeyboardFocusable property true because it can gain keyboard focus.
  • When it has keyboard focus, set its UIA HasKeyboardFocus true, and set it false otherwise.
  • When it gains keyboard focus, have the control raise a UIA FocusChanged event, so that products like AIWin and Narrator are informed that keyboard focus has moved.
  • Ensure that every action at the control that can be triggered by the mouse, (such as invoke, select, expand/collapse, toggle, move, size, delete, etc,) can be intuitively and efficiently triggered using only the keyboard.


AIWin reporting the Tab order of the controls in the WPF Settings app.

Figure 9: AIWin reporting the Tab order of the controls in the WPF Settings app.


The question around why AIWin didn’t call out an issue with the unexpected Tab behavior, is a reminder that while the tool can be extremely helpful, it doesn’t call out all potential issues that might exist in your UI. AIWin is only one part of the set of resources available to you, as you work to deliver a great experience for all your customers. Another very important resource at your disposal is your own close attention to what’s happening in your UI as you interact with it.

I’ve yet to investigate whether the Tab issue in the Settings app is a result of something dodgy I’m doing with my own customization of the DataGrid. I’ll look into that sometime.

Now to resume the discussion…


Where’s keyboard focus?

When some new window appears in your app, it’s important to consider where keyboard focus should move to.

Note: In some types of UI, it’s worth considering whether keyboard focus should move at all following some action which results in new UI appearing. For example, say I have tab-like UI, with a set of tab items which can be selected to have a related nearby area repopulated with a set of controls related to the tab item. Often when a tab item is selected in this case, your customer may not want keyboard focus to move into the repopulated area because they want to select a few different tab items and consider what gets revealed, before moving into the repopulated area of most interest. So sometimes, whether keyboard focus moves depends on what you feel will be most helpful for your customers. But for this article, I’ll discuss the scenario of a window appearing, and keyboard focus moving into it.

Exactly which control gets keyboard focus when a window appears, depends on what you feel would be most helpful for your customers. Often it’s the first control in the window, but if you feel in the majority of cases your customers would want to start interacting with another particular control, you could consider setting focus to that control when the window appears. But an important point here is be consistent. Your customers won’t want to have to examine a window carefully every time it appears, just in order to figure out where keyboard focus is. Rather if focus is always set to the first control, or always set to that very popular other control, then after becoming familiar with your app, your customers can predict what’s going to happen when the window appears and move rapidly on to complete their tasks.

Having said that, I did discuss a bug recently where a WinForms app was setting keyboard focus to a control when a window appeared, and keyboard focus feedback was not shown visually on the control. So the app dev had understandably begun investigating how to force keyboard focus to appear on that control.

But in fact the app was behaving exactly as expected, and this is why…

In some situations, Windows tries to anticipate whether showing keyboard focus feedback is what your customer is most likely to want. So say a WinForms app is run, and keyboard focus moves to a window in the app. Exactly how helpful it would be to show keyboard focus feedback on the control, may be affected by what action the customer took to reach that window in the app. For example, a few weeks ago I built a test app and pinned it to the taskbar. When I clicked with the mouse on the app icon on the taskbar, the app appeared, and focus was set to the first control in the app. In that case keyboard focus feedback did not appear. That was because I launched the app with the mouse, and a customer using the mouse may not be interested in having keyboard focus feedback shown. I then launched the app from the taskbar via the keyboard. (That is, Win+T to move keyboard focus to the taskbar, RightArrow to my app icon, and press Enter to launch the app.) So my app’s UI was shown, and because I’d only used the keyboard to reach it, when the app set focus to the first control, keyboard focus feedback did appear.

So it may be by design for the control with keyboard focus to not show feedback in a WinForms app, if only the mouse had been used to reach that point. If, on the other hand, the keyboard had been involved with the steps of showing a window, keyboard focus feedback may automatically appear at the focused control in the window.

Note: I said “may” just then because I suspect that are other factors going on here. I just re-ran the test app that I built a few weeks ago, and now keyboard focus doesn’t appear on the focused control when my window appears, regardless of whether I used the keyboard to launch the app. I don’t know why I’m not getting the behavior that I originally did. But I’ll leaving the above details in, because it might explain behavior you get in your own WinForms app.

In the case where the window appears without keyboard focus being shown, and the customer wants to know where keyboard focus is, they would press Tab to move focus between controls. If they then want to move back to the control that first had focus, they’d press Shift+Tab.

On a side note, there is a setting in the Ease of Access Settings app called “Underline access keys when available”. With that turned on, the access keys in the Settings window in both the WinForms and the WPF app will always be shown, which could be very helpful to some customers. I suspect that while that settings relates to shortcuts and access keys, a side effect of checking that is that keyboard focus feedback will always be shown on the focused control when a window appears, regardless of whether mouse input or keyboard input had led to the control being shown. So that’s worth keeping in mind when comparing the behavior of apps on two different machines.

Important: Regarding how to set keyboard focus to a control in a WinForms app, as Control.Focus Method states, “Focus is a low-level method intended primarily for custom control authors. Instead, application programmers should use the Select method …”. In the case of the WinForms demo app, I call the ComboBox’s Select().


On another side note, it’s worth mentioning that WinForms also supports the concept of an Accept Button, (or what I tend to consider the “default button”). That button will get invoked in response to a press of Enter, regardless of what has keyboard focus. I discussed a bug recently related to an app apparently responding to Enter incorrectly, because the button with keyboard focus did not respond to the Enter press. What’s more, the person who logged the bug thought the Accept Button did have keyboard focus because it was rendered in a way that the other buttons weren’t. So anyone building or testing WinForms app, (or Win32 apps for that matter,) needs to understand the concept of the Accept Button, otherwise time will be lost discussing UI behavior that’s actually behaving by design.

Personally, I do wonder if Accept Buttons should just be avoided now. I can visit the Run dialog, and press Enter while keyboard focus is in the edit control, and whatever button is the Accept Button will be invoked. But if I try pressing Enter while naming a custom high contrast theme in the Ease of Access Settings app, then nothing happens. So what should I expect Enter to do? Well, fortunately WinForms provides a lot of support for you to deliver whatever experience you feel works best for your customers, and that includes setting (or not setting) a form’s AcceptButton.


Efficiently triggering action

Your customers will want all tasks that they complete in your app to be completed in as efficient a manner as possible. Yet even with the most intuitive Tab order, your customers might end up having to spend a while tabbing back and forth around the UI in order to reach some particular control of interest.

Well, both WinForms and WPF can help you and your customers with this, through use of “access keys”. Access keys can be used to invoke Buttons’ actions very quickly, or to move keyboard focus over to other types of controls for your customers to then interact with the controls however they’d like. To trigger a control’s access key, your customers can press the Alt key and the control’s access key together. Your customer can learn what access keys are available in a window by pressing and releasing the Alt key, which will result in all controls’ access keys being underlined in the text associated with the control.

In a WinForms app, access keys are typically set by adding an ampersand before the character to be the access key, in the text associated with the control. In WPF apps, access keys are typically set by adding an underline before that character.

Note: In WPF apps, for access keys to exist in the static text which precedes the control to be associated with the access key, (for example, in the TextBlock preceding a ComboBox’s,) the TextBlock would be replaced with a Label. The Label would then have a Target of the control of interest.

Note: After the little experimenting I did, I didn’t seem to find a way to set an access key for the link at the top of the Settings window. (That’s a LinkLabel in the WinForms app, and a Hyperlink embedded in a TextBlock in the WPF app.) I’ll keep experimenting, but for now, it’s a case of using an access key to set keyboard focus to a control near the link, and then tabbing to the link.


The same access keys being shown underlined in the WinForms app and WPF app.

Figure 10: The same access keys being shown underlined in the WinForms app and WPF app.


Note: Don’t forget to set access keys on your menu items too.

On a side note, details of the access keys that you set on your controls in the standard way described above, will get exposed by default through UIA. So as your customers using the Narrator screen reader navigate through the controls, they’ll be made aware of what access keys can be used, and so be enabled to more efficiently interact with those controls. Any important information like this must be exposed to all your customers, regardless of whether they leverage one or both of the visual and programmatic representation of your UI.


AIWin reporting that the access keys associated with a ComboBox is T, despite that access key not currently being underlined in the static text preceding the ComboBox in the UI.

Figure 11: AIWin reporting that the access key associated with a ComboBox is T, despite that access key not currently being underlined in the static text preceding the ComboBox in the UI.


Can two UI frameworks ever provide the same experience for your customers?

Some time back, a colleague and I were discussing what the correct keyboard behavior was with a set of radio buttons. That in itself is an interesting concept, as who’s to say what the correct behavior is? In many situations I can only express what I would expect to happen with some UI, based on my own experiences with UI. And just because some type of UI traditionally behaves in a particular way, that doesn’t necessarily mean that’s the way most customers would prefer.

But delivering a predictable experience based on the type of UI really does matter. The discussion with my colleague was prompted by his experience at a set of radio buttons. He was working at some control lying before a radio button, pressed Tab, and his screen reader announced that keyboard focus had moved to a radio button. He then pressed Tab again, and his screen reader announced that he was at a different radio button. His first assumption was that he moved from one group of radio buttons to another group of radio button, because he expected a press of Tab to move in and out of any group of radio buttons, and Arrow key presses to move between radio buttons in the same group.

However, that was not what had happened. Instead, pressing Tab had led to keyboard focus moving between radio buttons in the same group. He hadn’t expected that, and so additional time and effort was required by him to explore the UI, and to become familiar with how this specific UI behaved.

My colleague felt that this UI was not behaving as it should, and I felt the same.

For me, the following is how I expect radio buttons to behave. But please do keep in mind, what do I know? I’ve lost my copy of “The Bumper Book of How Things in the Multiverse are Meant to Work.”

  1. If a Tab key press moves keyboard focus into a group of radio buttons, then the next Tab key press should move keyboard focus out of that group.
  2. Once keyboard focus is in a radio button group, UpArrow and DownArrow key presses move keyboard focus to adjacent radio buttons, and loops keyboard focus within the group when at the end of the group.
  3. Today, whether a radio button automatically gets checked when it gets keyboard focus, (and all radio buttons in the same group get unchecked,) depends on what you think is the best experience for your customers. For example, the impact of an Ease of Access Settings “Select a color filter” radio button being checked is significant, and so it’s not automatically checked when it gets focus. But some other features do choose to auto-check radio buttons as the get focus. Personally, I prefer not auto-checking the radio buttons when they get focus, so that I can feel confident that I’m not inadvertently changing the state of anything simply while exploring the UI.


While by default, different UI frameworks for Windows desktop can behave differently, often action can be taken by app builders to deliver the experience that seems consistent with UI in general.

For my WinForms app, I think I got most of the radio button behavior by default. The only change I was hoping to make was to prevent the auto-checking of the radio buttons when they got keyboard focus, but I didn’t find a trivial way of doing that. If I set their AutoCheck properties to false, then I seem to be responsible for all the checking and unchecking of the radio buttons, and I don’t want to have to do that. So I lived with the behavior of auto-checking when the radio buttons get keyboard focus.

For the WPF app, by default I did get the not-auto-checking on keyboard focus behavior that I wanted, so I was happy about that. I explored whether in the interests of consistency with the WinForms app I would have the radio buttons auto-checked on keyboard focus, and I achieved this by calling Keyboard.AddKeyboardInputProviderAcquireFocusHandler() with the radio buttons, and explicitly checked the radio buttons on focus. I ultimately commented all that out, as while by default I want to be consistent with my WinForms app, I also feel strongly that I don’t want the auto-checking behavior.

There were a couple of specific changes I did need to make to the WPF app in order to get the focus-moving behavior I wanted. I achieve that by adding the following to the StackPanel which contains the radio buttons.

KeyboardNavigation.TabNavigation="Once"

KeyboardNavigation.DirectionalNavigation="Cycle">



One very interesting way in which the WinForms and WPF apps behave in the same way by default relates to the UIA hierarchy of the radio button UI. The radio buttons get exposed through UIA as children of their container. This means that when encountering one of the radio buttons, if a customer using a screen reader wants to have the context of the radio button announced, that context will include the container of the radio button.

For example, as I move keyboard focus out of the data grid in the WPF app’s Setting window, and on to the first radio button, Narrator might say the following:

“grandfather clock display, table exit, show both hands, radio button, selected, cap b”


So this means that keyboard focus has moved from a table, (which in this case is the DataGrid,) onto a radio button called “show both hands”, the radio button is inside a container called “grandfather clock display”, and it has an access key of “cap b”. The “cap b” thing isn’t quite accurate, as the access key is just ‘b’, but overall there’s a ton of helpful information in that announcement.

On a side note, if this was a Win32 app, and I’d built the UI with a Win32 GROUPBOX, then the radio buttons would not be exposed as children of the container, and so the group context would not be announced. In this case a Win32 app dev cannot reasonably take action to have the app expose the same UIA hierarchy as that exposed by a WinForms or WPF app, and they should not spend time trying to replicate that UIA hierarchy.


AIWin reporting that the WinForms radio buttons are children of the containing group element in the UIA tree.

Figure 12: AIWin reporting that the WinForms radio buttons are children of the containing group element in the UIA tree.


AIWin reporting that the WPF radio buttons are children of the containing group element in the UIA tree.

Figure 13: AIWin reporting that the WPF radio buttons are children of the containing group element in the UIA tree.


1001 things you need to know about “data grids”

A fair bit of discussion around data grids has cropped up recently, so I definitely think it’s worth sharing some thoughts on those.

Important: I’m very careful these days when I say “data grid”, as that could mean different things to different people. If I’m talking about WinForms, then I mean the DataGridView control, and if I’m talking about WPF, then I mean the DataGrid control. What I never, ever mean is the WinForms DataGrid control, as that’s an older, much less accessible control, and has been replaced with the WinForms DataGridView. If you happen to be using a WinForms DataGrid control in your app, please help your customers by replacing it with a DataGridView.


Thing #1: Do you know yourself how your customers can use your data grid?

I discussed a bug recently where it seemed that customers using the keyboard had to tab through every cell in a data grid in order to reach the control following the data grid. That would indeed be an unacceptable experience if it were true, given how inefficient it would be to navigate through the UI. It turned out that for that specific data grid, while tabbing did move keyboard focus between cells, a press of Ctrl+Tab or Shift+Ctrl+Tab would move focus forward or backwards out of the data grid just fine.

So there was no bug here.

This is a reminder of how it’s really important for app builders to know how the controls they’re adding to their app behave. For example, most customers using the keyboard know that a press of Alt+DownArrow should result in a focused ComboBox showing its dropdown, but does the dev who implemented the ComboBox know that?

In fact data grids support a great deal of interesting keyboard functionality. For WinForms DataGridViews, Default keyboard and mouse handling in the Windows Forms DataGridView control lists a big bunch of interesting keyboard shortcuts. And for WPF, there’s Default Keyboard and Mouse Behavior in the DataGrid Control. The WinForms resource describes how Ctrl+Tab and Shift+Ctrl+Tab can move keyboard focus out of the data grid, and while I couldn’t find similar details in the WPF resource, the WPF data grid seems to behave in the same way.

Based on the WinForms documentation, I was tempted to change the DataGridView’s StandardTab property, as I’d prefer tab not to move between cells. But instead I left it with the default StandardTab behavior in order to deliver a consistent tabbing experience across the two apps.

Important: While it’s certainly preferred for you as an app builder to know how your UI behaves, it’s often essential for your customers to know. If some keyboard shortcut is in practice required to be used in order for tasks at the UI to be completed, what good is the shortcut if customers don’t know it’s there? Someone’s got to make sure your customers know about all the powerful keyboard functionality in your app, and one way to do that is for your Help content to have an easy-to-reach Keyboard section which describes all the things your customers need to know when using your app with the keyboard.

Important: Going back to what someone means when they say “data grid”, there’s also a UWP XAML DataGrid in the Windows Community Toolkit. As far as I know, at the time that I write this, that control’s not accessible, and that issue is being tracked at DataGridRows not accessible in Release mode. That’s worth keeping in mind if you’re considering adding it to your app.


Thing #2: Can only some of your customers leverage the functionality in your data grid?

Say your app presents a data grid jam-packed with tons of helpful data, all nicely presented in columns. Realistically, your customers can often only complete their tasks if the data can be sorted by column, and in addition, customers must be able to set the width of each column to be whatever works best for them.

So you take action to enable customers using the mouse to click on a column header to sort the data by that column, and to drag some clickable area in the column headers to resize columns. Great! Your customers who can use the mouse will be so pleased.

But what about your customers who only use the keyboard?

Well, for WinForms app, as Default keyboard and mouse handling in the Windows Forms DataGridView control states, if your DataGridView has a DataGridViewColumn.SortMode property value of Automatic, then a press of F3 will sort the data grid by the focused column, but only if you’re leveraging a recent version of .NET Framework. This is yet another reason why it’s so important to your customers for your app to leverage the accessibility improvements in recent versions of .NET Framework if it’s at all practical for you to do so.

It’s also straightforward to have the WPF DataGrid’s column sortable via the keyboard. By adding the style below to your DataGrid’s DataGridColumnHeader, your customers can tab up to the column headers, arrow between them, and press Space to sort by the focused column. WPF takes care of handling all the key-related events and the rendering of the icon which indicates what the current sort column is. Pretty useful really.

<Style TargetType="{x:Type DataGridColumnHeader}">
   <Setter Property="Focusable" Value="True" />
</Style>


Regarding functionality to support changing column widths via the keyboard, again, when leveraging the accessibility improvements in recent versions of .NET Framework, a WinForms DataGridView column can be resized using Alt+Left/Right arrow. (That doesn’t seem to be mentioned at Default keyboard and mouse handling in the Windows Forms DataGridView control but I believe it to be true.) I’ve not found a straightforward way of resizing columns in the WPF DataGrid with the keyboard, and it would take too long for me now to explore handling key events myself to achieve the desired results.

This is an example of how sometimes as an app developer, it’s practical to achieve some desired functionality, and sometimes it isn’t. If you have customer feedback relating to a strong need to add support for particular accessibility-related functionality to a WinForms or WPF control, please do log the issue at .NET at github. I logged the following issues myself just the other day.


I’d strongly recommend that you spend some time running through the sorts of tasks that you believe your customers will perform at your UI, using only the keyboard. If you find that you’re blocked from some functionality which is available to customers using the mouse, please update the UI to support the customers who only use the keyboard if at all practical.


Thing #3: What about the screen reader experience at your data grid?

This next bit isn’t really specific to keyboard accessibility, but given that we’re talking about data grids, I thought it worth describing some real-world bugs I learnt of recently with the programmatic accessibility of data grids.

Some app builders like to show a column in their data grid, and the column’s cells don’t show any text. Rather the cells only show an icon. The idea is that the icon’s visuals convey some helpful information to the customer. Personally, I feel that discerning the meaning of some tiny icon can be a challenge for many customers, both from a visual and a cognitive perspective. And when rows in a column may show two very similar tiny icons, but with a few pixels different indicating some difference in state, things get even more challenging. So please consider whether it would be more helpful to show text in the column rather than an icon.

What’s more, some app builders may choose to put no text in the column header for the column showing the icons. They may feel it’s obvious what the column’s about, because it’s showing those icons. But rather than have customers make their best guess about the meaning of the column, why not just make it easy for them and put text in the column header?

But say for some particular reason, you really feel it’s best for your customers to show icons in a column, and give the column no header text.

What about the resulting experience for your customers who use screen readers at the data grid? The real-world bugs that I'd learnt of recently, related to the programmatic representation of data grids like this.

The following image shows AIWin reporting that a cell showing an icon in a WinForms DataGridView has nothing in its UIA Name to indicate the purpose of the cell, nor anything in its UIA Value to indicate the state of the icon. (Ok, it has a Value of “System.Drawing.Bitmap”, but perhaps that isn’t the most helpful information for the app’s customers.)


AIWin reporting that the UIA Name and Value properties for a DataGridView cell showing an icon, convey no useful information about the purpose or state of the cell.

Figure 14: AIWin reporting that the UIA Name and Value properties for a WinForms DataGridView cell showing an icon, convey no useful information about the purpose or state of the cell.


However, with the help of a custom AccessibleObject for the DataGridView cell, the cell’s AccessibleObject.Name and AccessibleObject.Value can be overridden to be whatever would be most helpful for your customers. So let’s consider the column in the demo app to be a “Status” column, and when an icon’s shown in a cell, it means that the Status is “Running”.


AIWin reporting that the UIA Name and Value properties for a WinForms DataGridView cell showing an icon, now include the text “Status” and “Running” respectively.

Figure 15: AIWin reporting that the UIA Name and Value properties for a WinForms DataGridView cell showing an icon, now include the text “Status” and “Running” respectively.


A similar situation exists for a WPF DataGrid with a column showing icons in its cell, and with no text in the column header. The image below shows AIWin reporting that by default the UIA Name of the cell is something based on the namespace and class of the UI, (which is of no help to customers,) and when the UIA TableItem pattern is used to access the Name of the column header, it gets back an empty string.


AIWin reporting that the UIA Name property for a WPF DataGrid cell showing an icon, and the Name of the associated column header, convey no useful information about the purpose or state of the cell.

Figure 16: AIWin reporting that the UIA Name property for a WPF DataGrid cell showing an icon, and the Name of the associated column header, convey no useful information about the purpose or state of the cell.


But once again, with a few adjustments, the cell and its associated column header can be given UIA Names which will be announced by the Narrator screen reader as customers using Narrator navigate around the data grid.


AIWin reporting that the UIA Name properties for a WPF DataGrid cell showing an icon, and the UIA Name of the associated column header, now include the text “Running” and “Status” respectively.

Figure 17: AIWin reporting that the UIA Name properties for a WPF DataGrid cell showing an icon, and the UIA Name of the associated column header, now include the text “Running” and “Status” respectively.


Now having just reviewed the above images, I’ve realized that I forgot to take action to set the UIA Name property of the rows in the WPF DataGrid. By default the Name of the cell's parent UIA element is something based on the namespace and class of the data item, and that's not helpful to my customers. To address this, I overrode ToString() in my data class, and had it return whatever would efficiently help my customers decide whether the row is of interest to them when they encounter it. For this demo app, I simply set the Name to be the content of the row’s cell in the “Name” column, but often a shipping app might include data from more than one column, (while still keeping less important cell data out of the Name).


AIWin reporting that the UIA Name property for one of the UIA dataitem elements representing a WPF DataGrid row, is "Big Ben".

Figure 18: AIWin reporting that the UIA Name property for one of the UIA dataitem elements representing a WPF DataGrid row, is "Big Ben".


Important: It’s worth recognizing here that WinForms and WPF data grids don’t expose their contained data through UIA in the same way. All an app builder can do is work to have whatever UI they show visually in their data grid, exposed in whatever way the UI framework supports. The most important thing is that none of your customers are blocked from any important information, regardless of how they interact with their devices.

Having now described 3 things you need to know about data grids, I’ve spent all day writing this article, and I’ve now run out of time. So the other 998 things you need to know about data grids will have to wait until “Part 2” of this series.


Summary

There really are so many details relating to keyboard accessibility, that it would take way more time for me to write them down than I have time for now. And I don’t know them all anyway. But many of the topics described in this article are critically important to your customers, and many are almost certainly relevant to your UI. Often it’s absolutely practical for you to tune your app’s keyboard experience in such a way that all your customers who only use the keyboard are empowered to achieve what they need to achieve through all your app’s great functionality.

A few years back I unplugged my mouse for nine months, and used my keyboard exclusively for input at my work machine. It really was quite a learning experience for me. I’m not suggesting you do the same, but please do spend some serious time using only your keyboard for accessing all the features in your app. If you feel you don’t have time to do that, then you already know there’s a problem. If you don’t have time to use your app with a keyboard, why would you expect your customers who can only use apps with the keyboard to have time?

And the reminder again, if you learn of accessibility issues with WinForms or WPF UI which is impacting you or your customers, please do log the issues at .NET at github. While the WinForms and WPF UI frameworks provide a great deal of support for building accessible features, and .NET has had many great accessibility improvements made to it over recent years, I’m sure there are still opportunities to improve things even further. Your feedback can help the .NET team prioritize their future work.

And having written all that, I’m left wondering whether in some situations, one-handed clocks could lead to a more peaceful, less harried, world. Do we really need to be constantly aware of the exact time? The kettle’s going to take just as long to boil.

Guy

要查看或添加评论,请登录

Guy Barker的更多文章

社区洞察

其他会员也浏览了