Your customers who are blind or have low vision use screen readers like Narrator to inform them of what's going on in your app. Narrator will always describe something about the UI that's highlighted with its blue rectangular cursor, as that UI is probably of most interest to your customer at any given moment. For example, when keyboard focus moves to a button and the Narrator cursor is moved along with it, your customer hears the accessible name of the button, followed by "Button". Or if your customer's moved the Narrator cursor to a progress bar that has specific progress percentage values available on it, then as the progress changes, Narrator will announce the new progress values.
However, often your customer will not be informed of UI changes which occur at some point on the screen which is distant from where the Narrator cursor currently is. This is intentional, as to announce such things could often be a big distraction. But this also means that your customer may not be informed of some information that's actually very important to them. For example, say your customer invokes a button and in response a progress bar appears while some operation is performed. If they hear nothing when the progress bar appears, they may wonder if they actually invoked the button or not. And if the operation later completes with an error, and some TextBlock is updated to show a related error string, if your customer hears nothing at that time, they may sit waiting for the operation to succeed and return some useful result to them.
XAML app devs can help their customers by following the two steps described below.
You know when your UI is being updated to present important visuals which customers who are blind should be made aware of. So get all such cases listed in your feature spec, in a section specific to the screen reader user experience. For example, notify your customer when progress bars or error-related text strings appear.
In order for your customer to be informed of a change in your UI when the Narrator cursor is not explicitly pointing at the UI, you need to do two things:
1. Set the AutomationProperties.LiveSetting property on the element of interest. This makes the UI a "Live region". (The term "Live region" originates from web UI terminology, but "LiveSetting" and "Live region" are used pretty much interchangeably these days when discussing XAML.) When you're setting the AutomationProperties.LiveSetting property on an element, you'll set this to either "Assertive" or "Polite". The value you choose is your recommendation to a screen reader on how urgently you'd like the screen reader to inform your customer of a change to the UI. "Assertive" means you'd like your customer to be notified immediately, (interrupting the screen reader's current in-progress speech if there is any,) and "Polite" means you'd like whatever in-progress speech there happens to be, to be completed before informing your customer of the change to your UI.
The following is a very basic example with a (not localized) TextBlock.
<TextBlock x:Name="MyTextBlock" Text="No error" AutomationProperties.LiveSetting="Assertive" />
2. When the data changes on your element, you must explicitly raise an event to let the screen reader know that some data associated with the element has changed.
For example, with the TextBlock in the earlier snippet:
var peer = FrameworkElementAutomationPeer.FromElement(MyTextBlock);
if (peer != null)
By raising the event, you’re only telling the screen reader that something's changed on the element of interest. You’re not saying what's changed, and you are not specifying exactly what you want your customer to be told. You are simply saying the element's changed, and the screen reader can go on to take whatever action it feels is appropriate.
To illustrate how important this all this, some time back I worked with a gentleman who was blind and had ALS/MND, and he was working hard to try to learn how to send e-mail. He wasn’t proficient with the e-mail package or screen reader, and so needed all the help he could get from the features he was using. But there were times when important UI was displayed in the e-mail app, and his screen reader said nothing. Despite all his efforts, he never became familiar enough with the e-mail app to send e-mail by himself.
Please do consider how you can use LiveSettings in your XAML app to make your customers aware of important things going on in your UI.
1. By setting the LiveSetting property on the element, will the LiveRegionChanged event ever get raised automatically for me?
No. You must always raise the event yourself when appropriate.
2. Does the order of setting the new data on the element and raising the LiveRegionChanged event matter?
Yes. Element data might be sent to the screen reader with the event itself, so you'll want the new data set on the element by the time you raise the event.
3. Once I've set the current data on the element and raised the LiveRegionChanged event, can I immediately set the data on the element to some new value?
While some screen readers will request that current data is sent up with the event, others may decide to request current data in response to them receiving the event. So be aware that the data set on the element for a short time after the event gets raised might be what gets ultimately supplied to your customer.
4. What if I want to notify the screen reader of some property change unrelated to text, eg enabled state of the UI?
When you raise the LiveRegionChanged event, you're not notifying the screen reader of a change in any particular property, rather you're simply saying that the element's changed and your customer should be informed. It's up to the screen reader to decide what to tell your customer in response. In Narrator's case, your customer will often be told whatever they'd hear when explicitly moving Narrator to the element.
5. How does the visibility of the element fit in with this?
The call to FrameworkElementAutomationPeer.FromElement() in the snippet above will return null if the element's not visible. So watch out for the possibility of trying to dereference a null AutomationPeer here. This can get interesting when you've bound the text to be shown visually on the element to some view model property, and also bound the visibility of the element to the same view model property. But in practice, this should still result in the LiveRegionChanged event being raised as required.
6. How do I know the LiveRegionChanged is being raised when I run my app?
Run the AccEvent SDK tool and set it up to report all LiveRegionChanged events being raised. The following image shows the AccEvent tool reporting the LiveRegionChanged event being raised by the above code snippet, (once the text on the TextBlock's been set to the new value of "Danger! Danger!".
Figure 1: The AccEvent SDK tool reporting a LiveRegionChanged event.
7. I can see my event in AccEvent, but Narrator's not announcing anything. Why not?
Often when we raise LiveRegionChanged events, we’re also doing other things with our UI around the same time which leads to keyboard focus moving. Narrator will always announce a keyboard focus change to your customer, and that will interrupt whatever Narrator was saying (or about to say) in response to events received prior to the FocusChanged event. So use AccEvent to see if a FocusChanged event was raised soon after the LiveRegionChanged event. If it is, then it’s possible that Narrator’s announcement about the keyboard focus change stomped on the announcement relating to your LiveRegionChanged event.
8. Narrator announces something relating to my LiveRegionChanged event way later than I expect. Why?
In response to receiving the LiveRegionChanged event, Narrator may request more information about your UI. If your feature is busy doing some operation in such a way that the thread involved with servicing the request from Narrator (via UI Automation) is unresponsive, then Narrator will be blocked until your thread frees up. As a result, the announcement to your customer gets delayed. So consider if some threads in your feature are blocked shortly after the LiveRegionChanged events are raised.
9. Should I raise a LiveRegionChanged event every time a progress bar value changes?
Probably not. Be careful not to flood your customer with data. Certainly let them know that the progress bar's appeared.
Note: Don't forget to give a progress bar a useful name. If your progress bar has no accessible name, then the screen reader won't be able to inform your customer of the purpose of the progress bar. For example, if you show rolling dots progress UI while calculating the number of stars in the sky, give the progress bar a name of (say) "Calculating the number of stars in the sky…".
While you don't want to distract your customer, if you feel progress UI is going to be visible for a long time, you could consider raising a LiveRegionChanged event periodically. But by default, just make sure your customer is aware of the progress UI by announcing it when it appears. Once they know of the existence of the UI, they can move Narrator to it whenever they want to learn the current state of the progress.
10. Should a LiveRegionChanged event be raised when the enabled state of a button changes?
By default, no. While announcing a change in the enabled state of a button might seem potentially helpful to your customer, given that all they would hear is the same thing as when they've tabbed to the button, this could be confusing. For example, say they've invoked a radio button and in response a "Download" button elsewhere on the page becomes enabled. If after the radio button is invoked your customer heard something about the radio button, followed by "Download, Button", they may wonder if keyboard focus had automatically moved to the Download button.