diff options
author | Bret Johnson <bret.johnson@microsoft.com> | 2018-06-25 19:36:35 +0300 |
---|---|---|
committer | Bret Johnson <bret.johnson@microsoft.com> | 2018-06-25 20:27:10 +0300 |
commit | 180d2d95ab726946768cb0251da54c85585c5f59 (patch) | |
tree | d55ea853e19c92517949cd7ed2e6d37ca55d1b15 | |
parent | 533c6ee659cf0174436937ffb4617b6ba562abd6 (diff) |
[a11y][WPF] Added accessibilty support for popover windows
Previously, we weren't setting the automation Name on the window for
popovers, which this PR addresses, fixing:
https://devdiv.visualstudio.com/DevDiv/_workitems/edit/594024
https://devdiv.visualstudio.com/DevDiv/_workitems/edit/595199
Popovers are special for a couple of reasons:
- On the Xwt side, they derive from XwtComponent instead of XwtWidget.
- On the WPF side, accessibility info for the window should be set on the
PopupRoot, which doesn't exist until the popup is actually shown. Thus
we need to delay setting the accessibility settings until the popup is
shown.
These changes pass along accessbility settings for the non-widget
popovers, hold onto them, and delay setting them until the Show.
-rw-r--r-- | Xwt.Gtk/Xwt.GtkBackend/AccessibleBackend.cs | 5 | ||||
-rw-r--r-- | Xwt.WPF/Xwt.WPFBackend/AccessibleBackend.cs | 50 | ||||
-rw-r--r-- | Xwt.WPF/Xwt.WPFBackend/DropDownButton.cs | 1 | ||||
-rw-r--r-- | Xwt.WPF/Xwt.WPFBackend/PopoverBackend.cs | 31 | ||||
-rw-r--r-- | Xwt.WPF/Xwt.WPFBackend/WindowsSpinButton.xaml.cs | 20 | ||||
-rw-r--r-- | Xwt.XamMac/Xwt.Mac/AccessibleBackend.cs | 5 | ||||
-rw-r--r-- | Xwt/Xwt.Accessibility/Accessible.cs | 24 | ||||
-rw-r--r-- | Xwt/Xwt.Accessibility/IAccessibleBackend.cs | 4 | ||||
-rw-r--r-- | Xwt/Xwt/Popover.cs | 13 |
9 files changed, 131 insertions, 22 deletions
diff --git a/Xwt.Gtk/Xwt.GtkBackend/AccessibleBackend.cs b/Xwt.Gtk/Xwt.GtkBackend/AccessibleBackend.cs index 2bd393cb..c9726150 100644 --- a/Xwt.Gtk/Xwt.GtkBackend/AccessibleBackend.cs +++ b/Xwt.Gtk/Xwt.GtkBackend/AccessibleBackend.cs @@ -62,6 +62,11 @@ namespace Xwt.GtkBackend Initialize (backend?.Widget, eventSink); } + public void Initialize (IPopoverBackend parentPopover, IAccessibleEventSink eventSink) + { + // Not currently supported + } + public void Initialize (object parentWidget, IAccessibleEventSink eventSink) { this.eventSink = eventSink; diff --git a/Xwt.WPF/Xwt.WPFBackend/AccessibleBackend.cs b/Xwt.WPF/Xwt.WPFBackend/AccessibleBackend.cs index 803a0511..49d5b2d9 100644 --- a/Xwt.WPF/Xwt.WPFBackend/AccessibleBackend.cs +++ b/Xwt.WPF/Xwt.WPFBackend/AccessibleBackend.cs @@ -5,6 +5,8 @@ using System.Text; using System.Windows; using System.Windows.Automation; using System.Windows.Automation.Peers; +using System.Windows.Controls.Primitives; +using System.Windows.Media; using Xwt.Accessibility; using Xwt.Backends; @@ -18,24 +20,59 @@ namespace Xwt.WPFBackend public bool IsAccessible { get; set; } + private string identifier; public string Identifier { get { return AutomationProperties.GetAutomationId (element); } - set { AutomationProperties.SetAutomationId (element, value); } + set { + identifier = value; + AutomationProperties.SetAutomationId (element, value); + } } + + private string label; public string Label { get { return AutomationProperties.GetName (element); } - set { AutomationProperties.SetName (element, value); } + set { + label = value; + AutomationProperties.SetName (element, value); + } } + + private string description; public string Description { get { return AutomationProperties.GetHelpText (element); } - set { AutomationProperties.SetHelpText (element, value); } + set { + description = value; + AutomationProperties.SetHelpText (element, value); + } } + + private Widget labelWidget; public Widget LabelWidget { set { + labelWidget = value; AutomationProperties.SetLabeledBy (element, (Toolkit.GetBackend (value) as WidgetBackend)?.Widget); } } + /// <summary> + /// In some cases (just Popovers currently) we need to wait to set the automation properties until the element + /// that needs them comes into existence (a PopoverRoot in the case of a Popover, when it's shown). This is + /// used for that delayed initialization. + /// </summary> + /// <param name="element">UIElement on which to set the properties</param> + public void InitAutomationProperties (UIElement element) + { + if (identifier != null) + AutomationProperties.SetAutomationId (element, identifier); + if (label != null) + AutomationProperties.SetName (element, label); + if (description != null) + AutomationProperties.SetHelpText (element, description); + if (labelWidget != null) + AutomationProperties.SetLabeledBy (element, (Toolkit.GetBackend (labelWidget) as WidgetBackend)?.Widget); + } + public string Title { get; set; } public string Value { get; set; } public Role Role { get; set; } = Role.Custom; @@ -59,6 +96,13 @@ namespace Xwt.WPFBackend wpfBackend.HasAccessibleObject = true; } + public void Initialize (IPopoverBackend parentPopover, IAccessibleEventSink eventSink) + { + var popoverBackend = (PopoverBackend) parentPopover; + Popup popup = popoverBackend.NativeWidget; + Initialize (popup, eventSink); + } + public void Initialize (object parentWidget, IAccessibleEventSink eventSink) { this.element = parentWidget as UIElement; diff --git a/Xwt.WPF/Xwt.WPFBackend/DropDownButton.cs b/Xwt.WPF/Xwt.WPFBackend/DropDownButton.cs index 5b7934d1..704cd442 100644 --- a/Xwt.WPF/Xwt.WPFBackend/DropDownButton.cs +++ b/Xwt.WPF/Xwt.WPFBackend/DropDownButton.cs @@ -159,7 +159,6 @@ namespace Xwt.WPFBackend owner.IsChecked = true; } - public void Collapse () { owner.IsChecked = false; diff --git a/Xwt.WPF/Xwt.WPFBackend/PopoverBackend.cs b/Xwt.WPF/Xwt.WPFBackend/PopoverBackend.cs index 3203682c..afedab29 100644 --- a/Xwt.WPF/Xwt.WPFBackend/PopoverBackend.cs +++ b/Xwt.WPF/Xwt.WPFBackend/PopoverBackend.cs @@ -1,4 +1,4 @@ -// +// // PopoverBackend.cs // // Author: @@ -58,10 +58,28 @@ namespace Xwt.WPFBackend get { return (Popover)base.frontend; } } - System.Windows.Controls.Primitives.Popup NativeWidget { + public System.Windows.Controls.Primitives.Popup NativeWidget { get; set; } + /// <summary> + /// Search up the visual tree, finding the PopupRoot for the popup. + /// </summary> + /// <returns>PopupRoot or null if not found for some reason</returns> + public FrameworkElement GetPopupRoot () + { + FrameworkElement element = Border; + + do { + element = (FrameworkElement) VisualTreeHelper.GetParent (element); + if (element == null) + return null; + + if (element.GetType ().Name == "PopupRoot") + return element; + } while (true); + } + public PopoverBackend () { Border = new System.Windows.Controls.Border { @@ -113,6 +131,15 @@ namespace Xwt.WPFBackend }; NativeWidget.PlacementTarget = (System.Windows.FrameworkElement)Context.Toolkit.GetNativeWidget (reference); NativeWidget.IsOpen = true; + + // Popups are special in that the automation properties need to be set on the PopupRoot, which only exists when the popup is shown + // See https://social.msdn.microsoft.com/Forums/vstudio/en-US/d4ba12c8-7a87-478e-b064-5620f929a0cf/how-to-set-automationid-and-name-for-popup?forum=wpf + var accessibleBackend = (AccessibleBackend)Toolkit.GetBackend (Frontend.Accessible); + if (accessibleBackend != null) { + FrameworkElement popupRoot = GetPopupRoot (); + if (popupRoot != null) + accessibleBackend.InitAutomationProperties (popupRoot); + } } void NativeWidget_Closed (object sender, EventArgs e) diff --git a/Xwt.WPF/Xwt.WPFBackend/WindowsSpinButton.xaml.cs b/Xwt.WPF/Xwt.WPFBackend/WindowsSpinButton.xaml.cs index 361b91b4..cb212fa4 100644 --- a/Xwt.WPF/Xwt.WPFBackend/WindowsSpinButton.xaml.cs +++ b/Xwt.WPF/Xwt.WPFBackend/WindowsSpinButton.xaml.cs @@ -168,8 +168,8 @@ namespace Xwt.WPFBackend mainGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) }); mainGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(16) }); - //Textbox - textBox = new SpinButtonTextBox (this); + //Textbox + textBox = new SpinButtonTextBox (this); textBox.Text = "0"; textBox.HorizontalAlignment = HorizontalAlignment.Stretch; textBox.MinWidth = 25; @@ -249,7 +249,7 @@ namespace Xwt.WPFBackend public TextBox TextBox => textBox; - private void UserControl_Loaded(object sender, RoutedEventArgs e) + private void UserControl_Loaded(object sender, RoutedEventArgs e) { UpdateTextbox(); } @@ -459,19 +459,19 @@ namespace Xwt.WPFBackend #endregion #region Accessibility - class SpinButtonTextBox : TextBox - { - WindowsSpinButton spinButton; + class SpinButtonTextBox : TextBox + { + WindowsSpinButton spinButton; public SpinButtonTextBox (WindowsSpinButton spinButton) { this.spinButton = spinButton; } - protected override AutomationPeer OnCreateAutomationPeer () - { - return new WindowsSpinButtonAutomationPeer (spinButton); - } + protected override AutomationPeer OnCreateAutomationPeer () + { + return new WindowsSpinButtonAutomationPeer (spinButton); + } } class WindowsSpinButtonAutomationPeer : UserControlAutomationPeer, IRangeValueProvider diff --git a/Xwt.XamMac/Xwt.Mac/AccessibleBackend.cs b/Xwt.XamMac/Xwt.Mac/AccessibleBackend.cs index e1aa0e14..24ccc6b6 100644 --- a/Xwt.XamMac/Xwt.Mac/AccessibleBackend.cs +++ b/Xwt.XamMac/Xwt.Mac/AccessibleBackend.cs @@ -49,6 +49,11 @@ namespace Xwt.Mac Initialize (parentBackend?.Widget, eventSink); } + public void Initialize (IPopoverBackend parentPopover, IAccessibleEventSink eventSink) + { + // Not currently supported + } + public void Initialize (object parentWidget, IAccessibleEventSink eventSink) { this.eventSink = eventSink; diff --git a/Xwt/Xwt.Accessibility/Accessible.cs b/Xwt/Xwt.Accessibility/Accessible.cs index 836754d3..3075737a 100644 --- a/Xwt/Xwt.Accessibility/Accessible.cs +++ b/Xwt/Xwt.Accessibility/Accessible.cs @@ -1,4 +1,4 @@ -// +// // Accessible.cs // // Author: @@ -50,9 +50,12 @@ namespace Xwt.Accessibility protected override void OnBackendCreated () { - var parentBackend = Parent.parentComponent?.GetBackend () as IWidgetBackend; - if (parentBackend != null) - Backend.Initialize (parentBackend, this); + object parentBackend = Parent.parentComponent?.GetBackend (); + + if (parentBackend is IWidgetBackend) + Backend.Initialize ((IWidgetBackend) parentBackend, this); + else if (parentBackend is IPopoverBackend) + Backend.Initialize ((IPopoverBackend) parentBackend, this); else Backend.Initialize (Parent.parentNativeObject, this); } @@ -72,6 +75,15 @@ namespace Xwt.Accessibility backendHost.Parent = this; } + internal Accessible (Popover parent) + { + if (parent == null) + throw new ArgumentNullException (nameof (parent)); + parentComponent = parent; + backendHost = new AccessibleBackendHost (); + backendHost.Parent = this; + } + internal Accessible (object nativeParent) { if (nativeParent == null) @@ -276,6 +288,10 @@ namespace Xwt.Accessibility { } + public void Initialize (IPopoverBackend parentPopover, IAccessibleEventSink eventSink) + { + } + public void Initialize (object parentWidget, IAccessibleEventSink eventSink) { } diff --git a/Xwt/Xwt.Accessibility/IAccessibleBackend.cs b/Xwt/Xwt.Accessibility/IAccessibleBackend.cs index 9ee3d9e1..a0f8be9d 100644 --- a/Xwt/Xwt.Accessibility/IAccessibleBackend.cs +++ b/Xwt/Xwt.Accessibility/IAccessibleBackend.cs @@ -1,4 +1,4 @@ -// +// // AccessibleBackendHandler.cs // // Author: @@ -32,6 +32,8 @@ namespace Xwt.Backends { void Initialize (IWidgetBackend parentWidget, IAccessibleEventSink eventSink); + void Initialize (IPopoverBackend parentPopover, IAccessibleEventSink eventSink); + void Initialize (object parentWidget, IAccessibleEventSink eventSink); bool IsAccessible { get; set; } diff --git a/Xwt/Xwt/Popover.cs b/Xwt/Xwt/Popover.cs index 1f2ada42..6edbdb8b 100644 --- a/Xwt/Xwt/Popover.cs +++ b/Xwt/Xwt/Popover.cs @@ -25,6 +25,7 @@ // THE SOFTWARE. using System; +using Xwt.Accessibility; using Xwt.Drawing; using Xwt.Backends; @@ -84,7 +85,17 @@ namespace Xwt VerifyConstructorCall (this); Content = content; } - + + Accessible accessible; + public Accessible Accessible { + get { + if (accessible == null) { + accessible = new Accessible (this); + } + return accessible; + } + } + public Widget Content { get { return content; } set { |