|
| 1 | +## Accessibility improvements in WPF |
| 2 | + |
| 3 | +### Scope |
| 4 | +Major |
| 5 | + |
| 6 | +### Version Introduced |
| 7 | +4.8 |
| 8 | + |
| 9 | +### Source Analyzer Status |
| 10 | +NotPlanned |
| 11 | + |
| 12 | +### Change Description |
| 13 | +**Tooltips show on Keyboard focus** |
| 14 | +In .NET Framework 4.7.2 and earlier versions, tooltips only display when a user hovers the mouse cursor over a WPF control. Starting with .NET Framework 4.8, , tooltips are displayed on keyboard focus as well as via a keyboard shortcut. |
| 15 | +To enable this feature, an application needs to target .NET Framework 4.8 or opt-in via [SystemColors.HighlightTextBrushKey](xref:System.Windows.SystemColors.HighlightTextBrushKey) `Switch.UseLegacyAccessibilityFeatures.3` and `Switch.UseLegacyToolTipDisplay`., as the following example shows: |
| 16 | +```xml |
| 17 | +<?xml version="1.0" encoding="utf-8" ?> |
| 18 | +<configuration> |
| 19 | + <startup> |
| 20 | + <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7" /> |
| 21 | + </startup> |
| 22 | + <runtime> |
| 23 | + <AppContextSwitchOverrides value="Switch.UseLegacyAccessibilityFeatures=false;Switch.UseLegacyAccessibilityFeatures.2=false;Switch.UseLegacyAccessibilityFeatures.3=false;Switch.UseLegacyToolTipDisplay=false" /> |
| 24 | + </runtime> |
| 25 | +</configuration> |
| 26 | +``` |
| 27 | + |
| 28 | +Once enabled, all controls containing a tooltip will display it once the control receives keyboard focus. The tooltip can be dismissed over time or when keyboard focus changes. Users can also dismiss the tooltip manually with a new keyboard shortcut, Ctrl + Shift + F10. Once the tooltip has been dismissed, it can be displayed again by using the same keyboard shortcut. |
| 29 | + |
| 30 | +Note: [RibbonToolTips](xref:System.Windows.Controls.Ribbon.RibbonToolTip) on [RibbonControls](xref:System.Windows.Controls.Ribbon.RibbonControl) don’t show on keyboard focus; they only show by using the keyboard shortcut. |
| 31 | + |
| 32 | +**Elements with Collapsed or Hidden visibility are no longer announced by screen readers** |
| 33 | +User interfaces containing <xref:System.Windows.Visibility.Collapsed?displayProperty=nameWithType> or <System.Windows.Visibility.Hidden?displayProperty=nameWithType> elements can be misrepresented by screen readers if such elements are announced to the user. In .NET Framework 4.8, WPF no longer includes Collapsed or Hidden elements in the Control View of the UIAutomation tree, so that screen readers no longer announce these elements. |
| 34 | + |
| 35 | +**SizeOfSet and PositionInSet Support** |
| 36 | +Windows 10 introduced the SizeOfSet and PositionInSet [UIAutomation properties](/windows/desktop/winauto/uiauto-automation-element-propids), which are used by applications to describe the count of items in a set. UIAutomation client applications such as screen readers can then query an application for these properties and announce an accurate representation of the application’s UI. |
| 37 | + |
| 38 | +This feature adds support for WPF applications to expose these two properties to UIAutomation. These two properties can be exposed to UIAutomation in either of two ways: |
| 39 | + |
| 40 | +- __Dependency Properties__<br/> The [SizeOfSet](xref:System.Windows.Automation.AutomationProperties.SizeOfSetProperty) and [PositionInSet](xref:System.Windows.Automation.AutomationProperties.PositionInSetProperty) [dependency properties](xref:System.Windows.DependencyProperty) have been added to the <xref:System.Windows.Automation.AutomationProperties> class. A developer can set their values via XAML: |
| 41 | +```xaml |
| 42 | + <Button AutomationProperties.SizeOfSet="3" |
| 43 | + AutomationProperties.PositionInSet="1">Button 1</Button> |
| 44 | + <Button AutomationProperties.SizeOfSet="3" |
| 45 | + AutomationProperties.PositionInSet="2">Button 2</Button> |
| 46 | + <Button AutomationProperties.SizeOfSet="3" |
| 47 | + AutomationProperties.PositionInSet="3">Button 3</Button> |
| 48 | +``` |
| 49 | + |
| 50 | + |
| 51 | +- __AutomationPeer virtual methods__<br/> The <xref:System.Windows.Automation.Peers.AutomationPeer.GetSizeOfSetCore> and <xref:System.Windows.Automation.Peers.AutomationPeer.GetPositionInSetCore> virtual methods have been added to the <xref:System.Windows.Automation.Peers.AutomationPeer> class. A developer can provide values for `SizeOfSet` and `PositionInSet` by overriding these methods: |
| 52 | +```csharp |
| 53 | + public class MyButtonAutomationPeer : ButtonAutomationPeer |
| 54 | + { |
| 55 | + protected override int GetSizeOfSetCore() |
| 56 | + { |
| 57 | + // Call into your own logic to provide a value for SizeOfSet |
| 58 | + return CalculateSizeOfSet(); |
| 59 | + } |
| 60 | + |
| 61 | + protected override int GetPositionInSetCore() |
| 62 | + { |
| 63 | + // Call into your own logic to provide a value for PositionInSet |
| 64 | + return CalculatePositionInSet(); |
| 65 | + } |
| 66 | + } |
| 67 | +``` |
| 68 | +- __Automatic Values__<br/>Items in [ItemsControls](xref:System.Windows.Controls.ItemsControl) provide a value for these properties automatically without additional action from the developer. If an ItemsControl is grouped, the collection of groups is represented as a set, and each group is counted as a separate set, with each item inside that group providing its position inside that group as well as the size of the group. Automatic values are not affected by virtualization. Even if an item is not realized, it is still counted toward the total size of the set and affects the position in the set of its sibling items. |
| 69 | + |
| 70 | +**ControllerFor property support** |
| 71 | +UIAutomation’s ControllerFor property returns an array of automation elements that are manipulated by the automation element that supports this property. This property is commonly used for Auto-suggest accessibility. ControllerFor is used when an automation element affects one or more segments of the application UI or the desktop. Otherwise, it is hard to associate the impact of the control operation with UI elements. This feature adds the ability for controls to provide a value for the ControllerFor property. |
| 72 | + |
| 73 | +A new virtual method has been added to the <xref:System.Windows.Automation.Peers.AutomationPeer> class: |
| 74 | +```csharp |
| 75 | + virtual protected List<AutomationPeer> GetControlledPeersCore() |
| 76 | +``` |
| 77 | +To provide a value for the ControllerFor property, simply override this method and return a list of [AutomationPeers](xref:System.Windows.Automation.Peers.AutomationPeer) for the controls being manipulated by this <xref:System.Windows.Automation.Peers.AutomationPeer>: |
| 78 | +```csharp |
| 79 | +public class AutoSuggestTextBox: TextBox |
| 80 | + { |
| 81 | + protected override AutomationPeer OnCreateAutomationPeer() |
| 82 | + { |
| 83 | + return new AutoSuggestTextBoxAutomationPeer(this); |
| 84 | + } |
| 85 | + |
| 86 | + public ListBox SuggestionListBox; |
| 87 | + } |
| 88 | + |
| 89 | + internal class AutoSuggestTextBoxAutomationPeer : TextBoxAutomationPeer |
| 90 | + { |
| 91 | + public AutoSuggestTextBoxAutomationPeer(AutoSuggestTextBox owner) : base(owner) |
| 92 | + { |
| 93 | + } |
| 94 | + |
| 95 | + protected override List<AutomationPeer> GetControlledPeersCore() |
| 96 | + { |
| 97 | + List<AutomationPeer> controlledPeers = new List<AutomationPeer>(); |
| 98 | + AutoSuggestTextBox owner = Owner as AutoSuggestTextBox; |
| 99 | + |
| 100 | + controlledPeers.Add(UIElementAutomationPeer.CreatePeerForElement(owner.SuggestionListBox)); |
| 101 | + return controlledPeers; |
| 102 | + } |
| 103 | + } |
| 104 | +``` |
| 105 | + |
| 106 | +**The Name Automation property is now correctly propagated for the DatePicker and RibbonComboBox controls** |
| 107 | +Starting with .NET Framework 4.8, the <xref:System.Windows.Controls.DatePicker> and <xref:System.Windows.Controls.Ribbon.RibbonComboBox> controls reflect the <xref:System.Windows.Automation.AutomationProperties.Name?displayProperty=nameWithType> value assigned to them when read by a screen reader. |
| 108 | + |
| 109 | +**Expanders in GroupItem headers are now announced by screen readers** |
| 110 | +Starting with .NET Framework 4.8, an <xref:System.Windows.Controls.Expander> in a re-templated <xref:System.Windows.Controls.GroupItem> header is announced by screen readers. |
| 111 | + |
| 112 | +**Improved focus move logic when WPF is hosted in Windows Forms** |
| 113 | +The focus no longer loops inside a WPF <xref:System.Windows.Controls.UserControl> instead of breaking out of it under some hosting scenarios. |
| 114 | + |
| 115 | +**Changed the padding header in GridView to not focusable** |
| 116 | +In the .NET Framework 4.7.2 and earlier versions, the automatically generated padding header in a <xref:System.Windows.Controls.GridView> control was focusable, creating a bad experience for keyboard-only users. Starting with .NET Framework 4.8, it is not focusable. |
| 117 | + |
| 118 | +**Fixed automation tree for plain ItemsControl** |
| 119 | +Fixed an accessibility problem where the automation tree exposed for a plain <xref:System.Windows.Controls.ItemsControl> (as opposed to a derived class like ListBox or DataGrid) was not correct. |
| 120 | + |
| 121 | +**Fixed bounding rectangles in Per-Monitor DPI** |
| 122 | +In .NET Framework 4.7.2 and earlier versions, certain bounding rectangles around text are drawn incorrectly by Accessibility tools when WPF is run in Per-Monitor Aware mode. Starting with .NET Framework 4.8, they are drawn correctly. |
| 123 | + |
| 124 | +</br> |
| 125 | + |
| 126 | +- [x] Quirked |
| 127 | +- [ ] Build-time break |
| 128 | + |
| 129 | +### Recommended Action |
| 130 | +__How to opt in or out of these changes__ |
| 131 | + |
| 132 | +In order for the application to benefit from these changes, it must run on the .NET Framework 4.8 or later. The application can benefit from these changes in either of the following ways: |
| 133 | +- It is recompiled to target the .NET Framework 4.8. These accessibility changes are enabled by default on WPF applications that target the .NET Framework 4.8 or later. |
| 134 | +- It targets the .NET Framework 4.7.2 or an earlier version and opts out of the legacy accessibility behaviors by adding the following [AppContext switch](https://docs.microsoft.com/dotnet/framework/configure-apps/file-schema/runtime/appcontextswitchoverrides-element) to the `<runtime>` section of the app config file and setting it to `false`, as the following example shows. |
| 135 | + |
| 136 | +```xml |
| 137 | + <?xml version="1.0" encoding="utf-8"?> |
| 138 | + <configuration> |
| 139 | + <startup> |
| 140 | + <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7"/> |
| 141 | + </startup> |
| 142 | + <runtime> |
| 143 | + <!-- AppContextSwitchOverrides value attribute is in the form of 'key1=true|false;key2=true|false --> |
| 144 | + <AppContextSwitchOverrides value="Switch.UseLegacyAccessibilityFeatures=false;Switch.UseLegacyAccessibilityFeatures.2=false;Switch.UseLegacyAccessibilityFeatures.3=false" /> |
| 145 | + </runtime> |
| 146 | + </configuration> |
| 147 | +``` |
| 148 | +Note that to opt in to the accessibility features added in .NET Framework 4.8, you must also opt in to the accessibility features of .NET Framework 4.7.2 and .NET Framework 4.7.1 as well. |
| 149 | +Applications that target the .NET Framework 4.8 or later and want to preserve the legacy accessibility behavior can opt in to the use of legacy accessibility features by explicitly setting this AppContext switch to `true`. |
| 150 | + |
| 151 | +### Affected APIs |
| 152 | + |
| 153 | +* `M:System.Windows.Automation.Peers.AutomationPeer.GetSizeOfSetCore` |
| 154 | +* `M:System.Windows.Automation.Peers.AutomationPeer.GetPositionInSetCore` |
| 155 | +* `M:System.Windows.Automation.Peers.AutomationPeer.GetControlledPeersCore` |
| 156 | +* `M:System.Windows.Automation.Peers.AutomationPeer.GetSizeOfSet` |
| 157 | +* `M:System.Windows.Automation.Peers.AutomationPeer.GetPositionInSet` |
| 158 | +* `M:System.Windows.Automation.Peers.AutomationPeer.GetControlledPeers` |
| 159 | +* `M:System.Windows.Automation.Peers.ContentElementAutomationPeer.GetSizeOfSetCore` |
| 160 | +* `M:System.Windows.Automation.Peers.ContentElementAutomationPeer.GetPositionInSetCore` |
| 161 | +* `M:System.Windows.Automation.Peers.UIElementAutomationPeer.GetSizeOfSetCore` |
| 162 | +* `M:System.Windows.Automation.Peers.UIElementAutomationPeer.GetPositionInSetCore` |
| 163 | +* `M:System.Windows.Automation.Peers.UIElement3DAutomationPeer.GetSizeOfSetCore` |
| 164 | +* `M:System.Windows.Automation.Peers.UIElement3DAutomationPeer.GetPositionInSetCore` |
| 165 | +* `M:System.Windows.Automation.Peers.DataGridCellItemAutomationPeer.GetSizeOfSetCore` |
| 166 | +* `M:System.Windows.Automation.Peers.DataGridCellItemAutomationPeer.GetPositionInSetCore` |
| 167 | +* `M:System.Windows.Automation.Peers.DateTimeAutomationPeer.GetSizeOfSetCore` |
| 168 | +* `M:System.Windows.Automation.Peers.DateTimeAutomationPeer.GetPositionInSetCore` |
| 169 | +* `M:System.Windows.Automation.Peers.GroupItemAutomationPeer.GetSizeOfSetCore` |
| 170 | +* `M:System.Windows.Automation.Peers.GroupItemAutomationPeer.GetPositionInSetCore` |
| 171 | +* `M:System.Windows.Automation.Peers.ItemAutomationPeer.GetSizeOfSetCore` |
| 172 | +* `M:System.Windows.Automation.Peers.ItemAutomationPeer.GetPositionInSetCore` |
| 173 | +* `M:System.Windows.Automation.Peers.MenuItemAutomationPeer.GetSizeOfSetCore` |
| 174 | +* `M:System.Windows.Automation.Peers.MenuItemAutomationPeer.GetPositionInSetCore` |
| 175 | +* `F:System.Windows.Automation.AutomationProperties.SizeOfSetProperty` |
| 176 | +* `M:System.Windows.Automation.AutomationProperties.SetSizeOfSet(System.Windows.DependencyObject,int)` |
| 177 | +* `M:System.Windows.Automation.AutomationProperties.GetSizeOfSet(System.Windows.DependencyObject)` |
| 178 | +* `F:System.Windows.Automation.AutomationProperties.PositionInSetProperty` |
| 179 | +* `M:System.Windows.Automation.AutomationProperties.SetPositionInSet(System.Windows.DependencyObject,int)` |
| 180 | +* `M:System.Windows.Automation.AutomationProperties.GetPositionInSet(System.Windows.DependencyObject)` |
| 181 | +* `F:System.Windows.Automation.AutomationElement.SizeOfSetProperty` |
| 182 | +* `F:System.Windows.Automation.AutomationElement.PositionInSetProperty` |
| 183 | +* `F:System.Windows.Automation.AutomationElementIdentifiers.SizeOfSetProperty` |
| 184 | +* `F:System.Windows.Automation.AutomationElementIdentifiers.PositionInSetProperty` |
| 185 | +* `F:System.Windows.Automation.AutomationElementIdentifiers.ControllerForProperty` |
| 186 | + |
| 187 | +### Category |
| 188 | +WPF |
| 189 | + |
| 190 | +<!-- |
| 191 | + ### Original Bug |
| 192 | + 410007 |
| 193 | + 433560 |
| 194 | + 488213 |
| 195 | + 503411 |
| 196 | + 520147 |
| 197 | + 542626 |
| 198 | + 559546 |
| 199 | + 614397 |
| 200 | + 617457 |
| 201 | + 646633 |
| 202 | +--> |
0 commit comments