Skip to content

Commit

Permalink
Fix screen reader issues with CardAction/Button/MenuItem/NavigationVi…
Browse files Browse the repository at this point in the history
…ewItem (#1235)

* Add narration support to CardAction

* Fix screen readers trying to read IconElement in some cases

* Add narration support to NavigationViewItem

* Button workaround appears to be unnecessary now
  • Loading branch information
Difegue authored and pomianowski committed Feb 1, 2025
1 parent cfe9496 commit 83763fe
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 1 deletion.
9 changes: 8 additions & 1 deletion src/Wpf.Ui/Controls/CardAction/CardAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
// All Rights Reserved.

// ReSharper disable once CheckNamespace
using System.Windows.Automation.Peers;

namespace Wpf.Ui.Controls;

/// <summary>
Expand Down Expand Up @@ -48,4 +50,9 @@ public IconElement? Icon
get => (IconElement?)GetValue(IconProperty);
set => SetValue(IconProperty, value);
}
}

protected override AutomationPeer OnCreateAutomationPeer()
{
return new CardActionAutomationPeer(this);
}
}
77 changes: 77 additions & 0 deletions src/Wpf.Ui/Controls/CardAction/CardActionAutomationPeer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
// Copyright (C) Leszek Pomianowski and WPF UI Contributors.
// All Rights Reserved.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Automation;
using System.Windows.Automation.Peers;

namespace Wpf.Ui.Controls;

internal class CardActionAutomationPeer : FrameworkElementAutomationPeer
{
private readonly CardAction _owner;

public CardActionAutomationPeer(CardAction owner)
: base(owner)
{
_owner = owner;
}

protected override string GetClassNameCore()
{
return "Button";
}

protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Button;
}

public override object GetPattern(PatternInterface patternInterface)
{
if (patternInterface == PatternInterface.ItemContainer)
{
return this;
}

return base.GetPattern(patternInterface);
}

protected override AutomationPeer GetLabeledByCore()
{
if (_owner.Content is UIElement element)
{
return CreatePeerForElement(element);
}

return base.GetLabeledByCore();
}

protected override string GetNameCore()
{
var result = base.GetNameCore() ?? string.Empty;

if (result == string.Empty)
{
result = AutomationProperties.GetName(_owner);
}

if (result == string.Empty && _owner.Content is DependencyObject d)
{
result = AutomationProperties.GetName(d);
}

if (result == string.Empty && _owner.Content is string s)
{
result = s;
}

return result;
}
}
42 changes: 42 additions & 0 deletions src/Wpf.Ui/Controls/Menu/MenuItem.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@
ContentSource="Header"
RecognizesAccessKey="True"
TextElement.Foreground="{TemplateBinding Foreground}" />
<!-- TextBlock added so that screen readers don't try to read Icon -->
<TextBlock Grid.Column="0"
Grid.ColumnSpan="3"
Width="{Binding Width, RelativeSource={RelativeSource AncestorType={x:Type MenuItem}}}"
Height="{Binding Height, RelativeSource={RelativeSource AncestorType={x:Type MenuItem}}}"
Margin="-10"
FontSize="1"
Opacity="0"
Text="{Binding AutomationProperties.Name, RelativeSource={RelativeSource AncestorType={x:Type MenuItem}}}"
Background="Black"/>
</Grid>

<Popup
Expand Down Expand Up @@ -181,6 +191,16 @@
ContentSource="Header"
RecognizesAccessKey="True"
TextElement.Foreground="{TemplateBinding Foreground}" />
<!-- TextBlock added so that screen readers don't try to read Icon -->
<TextBlock Grid.Column="0"
Grid.ColumnSpan="3"
Width="{Binding Width, RelativeSource={RelativeSource AncestorType={x:Type MenuItem}}}"
Height="{Binding Height, RelativeSource={RelativeSource AncestorType={x:Type MenuItem}}}"
Margin="-10"
FontSize="1"
Opacity="0"
Text="{Binding AutomationProperties.Name, RelativeSource={RelativeSource AncestorType={x:Type MenuItem}}}"
Background="Black"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
Expand Down Expand Up @@ -264,6 +284,17 @@
FontSize="11"
Foreground="{DynamicResource TextFillColorDisabledBrush}"
Text="{TemplateBinding InputGestureText}" />

<!-- TextBlock added so that screen readers don't try to read Icon -->
<TextBlock Grid.Column="0"
Grid.ColumnSpan="5"
Width="{Binding Width, RelativeSource={RelativeSource AncestorType={x:Type MenuItem}}}"
Height="{Binding Height, RelativeSource={RelativeSource AncestorType={x:Type MenuItem}}}"
Margin="-10"
FontSize="1"
Opacity="0"
Text="{Binding AutomationProperties.Name, RelativeSource={RelativeSource AncestorType={x:Type MenuItem}}}"
Background="Black"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
Expand Down Expand Up @@ -334,6 +365,17 @@
FontSize="{TemplateBinding FontSize}"
Symbol="ChevronRight20" />
</Grid>

<!-- Fix double narration from mousing over the button and then the associated text -->
<TextBlock Grid.Column="0"
Grid.ColumnSpan="3"
Width="{Binding Width, RelativeSource={RelativeSource AncestorType={x:Type MenuItem}}}"
Height="{Binding Height, RelativeSource={RelativeSource AncestorType={x:Type MenuItem}}}"
Margin="-10"
FontSize="1"
Opacity="0"
Text="{Binding AutomationProperties.Name, RelativeSource={RelativeSource AncestorType={x:Type MenuItem}}}"
Background="Black"/>
</Grid>
</Border>

Expand Down
6 changes: 6 additions & 0 deletions src/Wpf.Ui/Controls/NavigationView/NavigationViewItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Windows.Automation.Peers;
using System.Windows.Controls;
using System.Windows.Input;

Expand Down Expand Up @@ -438,6 +439,11 @@ protected override void OnMouseDown(MouseButtonEventArgs e)
e.Handled = true;
}

protected override AutomationPeer OnCreateAutomationPeer()
{
return new NavigationViewItemAutomationPeer(this);
}

private void OnMenuItems_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
SetValue(HasMenuItemsPropertyKey, MenuItems.Count > 0);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
// Copyright (C) Leszek Pomianowski and WPF UI Contributors.
// All Rights Reserved.

using System.Windows.Automation;
using System.Windows.Automation.Peers;

namespace Wpf.Ui.Controls;

internal class NavigationViewItemAutomationPeer : FrameworkElementAutomationPeer
{
private readonly NavigationViewItem _owner;

public NavigationViewItemAutomationPeer(NavigationViewItem owner)
: base(owner)
{
_owner = owner;
}

protected override string GetClassNameCore()
{
return "NavigationItem";
}

protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.TabItem;
}

public override object GetPattern(PatternInterface patternInterface)
{
if (patternInterface == PatternInterface.ItemContainer)
{
return this;
}

return base.GetPattern(patternInterface);
}

protected override AutomationPeer GetLabeledByCore()
{
if (_owner.Content is UIElement element)
{
return CreatePeerForElement(element);
}

return base.GetLabeledByCore();
}

protected override string GetNameCore()
{
var result = base.GetNameCore() ?? string.Empty;

if (result == string.Empty)
{
result = AutomationProperties.GetName(_owner);
}

if (result == string.Empty && _owner.Content is DependencyObject d)
{
result = AutomationProperties.GetName(d);
}

if (result == string.Empty && _owner.Content is string s)
{
result = s;
}

return result;
}
}

0 comments on commit 83763fe

Please sign in to comment.