Last week, I worked on some accessibility issues. One of them was the windows narrator reading collapsed content. Hidding the content from the narrator was a little harder than I would have expected when starting working on the issues.
The WPF application is using an MVVM pattern where controls are hidden and shown by changing the Visibility property. For users that look at the screen, this works fine. However, when navigating the screen with the windows narrator, the collapsed content was still read to the user. When looking deeper, content in WPF that is collapsed is removed from the visual tree and should not be read by the narrator.
Fortunately, I found some hints to make a workaround. By overriding the OnCreateAutomationPeer
method, you can give a hint to the narrator to read or not read the element. The method IsControlElementCore
in the AutomationPeer
class gives a hint on if a control is a Control element if so the narrator skips the control. This can be used to skip your content when hidden:
public class MyButton : Button { protected override AutomationPeer OnCreateAutomationPeer() { return new MyAutomationPeer(this); } } public class MyAutomationPeer : ButtonBaseAutomationPeer { public MyAutomationPeer(Button button): base(button) {} protected override bool IsControlElementCore() { return Owner.Visible != Visiblity.Collapsed; } }
In the XAML I updated the controls to my own controls and now the collapsed content is hidden from the narrator.
Some links:
https://stackoverflow.com/questions/50125307/narrator-reads-hidden-elements-in-scan-mode
https://social.msdn.microsoft.com/Forums/azure/en-US/42d6b7b3-6378-4cbb-9209-24bcd0d47e73/narrator-is-reading-wpf-controls-that-are-hidden-or-collapsed-how-to-stop-reading-controls-which?forum=wpf
Hi Peter. This really helped. I used the same for checkbox as well and it worked well! Thank you.
LikeLiked by 1 person
I need to implement something similar for views. When the narrator is reading my view, it first reads control on the base page (that are now hidden) and then reads the view controls. Can you please help me with it.
LikeLike
The code above checks on collapsed, not on hidden. So you have to collapse the view to remove it from the visual tree. I did add some code to check if a parent control is collapsed (in the IsControlElementCore method). Some controls are composed of multiple sub elements, which can lead to some problems. As last resort you can just remove the text when the control is collapsed. Getting to to work right is in most cases lots of try and retry. You can also try .net 4.8 preview release: https://go.microsoft.com/fwlink/?linkid=2032091. The accessibility should be improved a lot and if you are able to target a newer framework it will save you a lot of time.
LikeLike
Thank you Peter. Sorry to bother you. One last doubt. I implemented this same code for Button in my application. But now when the narrator focuses on button, it says “Directions Custom” instead of “Directions Button Double tap to select”. I was able to change it back to button using the following code:
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Button;
}
But it still does not say Double tap to select. Also on double tap, nothing happens on the focused button. Do you have any idea, how can we make it to retain it’s double tap functionality.
LikeLike
If you inherit your automationpeer from a ButtonAutomationPeer you have all behaviors of a button. The same goes for each other control type.
LikeLike
Hi Peter! Thank you for publishing this article! I currently face the same issue with my WPF app, and tried your solution. Unfortunately, although I set up my custom classes in the same way and updated my controls, it did not help. Is there anything that you might recommend to have it working?
LikeLike
Did you set some breakpoints to check if the classes are actually are used? you should be able to verify the returned values .
LikeLike
Yes, I did confirm that the classes are actually used. Strange why it still reads the collapsed content…
LikeLike
What is the .NET version you are using? It could be that in the newest version the issue is fixed. Without code is quite a challenge to figure out what fails.
LikeLike
My application currently targets .NET framework version 4.0. I don’t think the code should be the issue since I followed the above code exactly.
LikeLike
The code above in the post was done on 4.7.2. Many of the issues in 4.7.2 are probably fixed in 4.8. I would try to use a newer version of the .Net framework. 4.0 was EOL on 2016-01-12
LikeLike