Skip to content

Commit 0e8e557

Browse files
Fix comprehensive screen reader accessibility issues
This commit resolves several UX issues to significantly improve the keyboard and screen reader navigation flow: - Added AutomationProperties.Name to the loading screen grid, operation list resizers, and the ExpandCollapseOpList button. - Disabled IsTabStop on the custom TitleBar so it doesn't get focused unnecessarily. - Fixed accessibility for view mode and filter options by assigning them grouping roles. - Added descriptive labels to the ToggleFiltersButton and MainToolbarButtonDropdown ('More actions'). - Bound AutomationProperties.Name to the dynamic text of MainToolbarButton so it correctly reads 'Install selected packages', etc. - Added AutomationProperties.Name to the BackButton on the package preferences dialog. - Added AutomationProperties.Name to the search mode radio buttons. - Removed 'ghost' tab stops following the Help button by explicitly setting IsTabStop='False' and OverflowButtonVisibility='Collapsed' on the CommandBar and surrounding layout containers. - Fixed CheckboxHeader by setting IsTabStop='False' and AccessibilityView='Raw'. - Improved package list item accessibility by building a custom AutomationPeer for PackageItemContainer that implements IToggleProvider and acts natively as a CheckBox role, allowing direct selection and status announcement without an extra tab stop. - Added AutomationProperties.Name and ToolTip labels ('User profile') to the dynamic GitHub login/cloud backup avatar UserControl in the main title bar.
1 parent 089a1be commit 0e8e557

8 files changed

Lines changed: 122 additions & 9 deletions

File tree

src/UniGetUI/Controls/PackageItemContainer.cs

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
using Microsoft.UI.Xaml.Automation;
2+
using Microsoft.UI.Xaml.Automation.Peers;
3+
using Microsoft.UI.Xaml.Automation.Provider;
14
using Microsoft.UI.Xaml.Controls;
25
using UniGetUI.PackageEngine.Interfaces;
36
using UniGetUI.PackageEngine.PackageClasses;
@@ -7,6 +10,78 @@ namespace UniGetUI.Interface.Widgets
710
public partial class PackageItemContainer : ItemContainer
811
{
912
public IPackage? Package { get; set; }
10-
public PackageWrapper Wrapper { get; set; } = null!;
13+
14+
private PackageWrapper _wrapper = null!;
15+
public PackageWrapper Wrapper
16+
{
17+
get => _wrapper;
18+
set
19+
{
20+
if (_wrapper != null)
21+
{
22+
_wrapper.PropertyChanged -= Wrapper_PropertyChanged;
23+
}
24+
_wrapper = value;
25+
if (_wrapper != null)
26+
{
27+
_wrapper.PropertyChanged += Wrapper_PropertyChanged;
28+
}
29+
}
30+
}
31+
32+
private void Wrapper_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
33+
{
34+
if (e.PropertyName == nameof(PackageWrapper.IsChecked))
35+
{
36+
var peer = FrameworkElementAutomationPeer.FromElement(this) as PackageItemContainerAutomationPeer;
37+
if (peer != null)
38+
{
39+
ToggleState oldState = !Wrapper.IsChecked ? ToggleState.On : ToggleState.Off;
40+
ToggleState newState = Wrapper.IsChecked ? ToggleState.On : ToggleState.Off;
41+
peer.RaiseToggleStatePropertyChanged(oldState, newState);
42+
}
43+
}
44+
}
45+
46+
protected override AutomationPeer OnCreateAutomationPeer()
47+
{
48+
return new PackageItemContainerAutomationPeer(this);
49+
}
50+
}
51+
52+
public partial class PackageItemContainerAutomationPeer : FrameworkElementAutomationPeer, IToggleProvider
53+
{
54+
private readonly PackageItemContainer _owner;
55+
56+
public PackageItemContainerAutomationPeer(PackageItemContainer owner) : base(owner)
57+
{
58+
_owner = owner;
59+
}
60+
61+
protected override AutomationControlType GetAutomationControlTypeCore()
62+
{
63+
return AutomationControlType.CheckBox;
64+
}
65+
66+
protected override object GetPatternCore(PatternInterface patternInterface)
67+
{
68+
if (patternInterface == PatternInterface.Toggle)
69+
{
70+
return this;
71+
}
72+
return base.GetPatternCore(patternInterface);
73+
}
74+
75+
public ToggleState ToggleState => (_owner.Wrapper != null && _owner.Wrapper.IsChecked) ? ToggleState.On : ToggleState.Off;
76+
77+
public void Toggle()
78+
{
79+
_owner.Wrapper?.IsChecked = !_owner.Wrapper.IsChecked;
80+
}
81+
82+
public void RaiseToggleStatePropertyChanged(ToggleState oldValue, ToggleState newValue)
83+
{
84+
RaisePropertyChangedEvent(TogglePatternIdentifiers.ToggleStateProperty, oldValue, newValue);
85+
}
1186
}
1287
}

src/UniGetUI/Controls/PackageWrapper.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,13 @@ public bool IsChecked
3131
{
3232
Package.IsChecked = value;
3333
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsChecked)));
34+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CheckedStatus)));
3435
_page.UpdatePackageCount();
3536
}
3637
}
3738

39+
public string CheckedStatus => IsChecked ? CoreTools.Translate("Checked") : CoreTools.Translate("Unchecked");
40+
3841
public bool IconWasLoaded;
3942
public bool AlternateIdIconVisible;
4043
public bool ShowCustomPackageIcon;

src/UniGetUI/MainWindow.xaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
PaneToggleRequested="TitleBar_PaneToggleRequested"
3737
SizeChanged="TitleBar_SizeChanged"
3838
Visibility="Collapsed"
39+
IsTabStop="False"
3940
>
4041
<!--TitleBar.IconSource>
4142
<ImageIconSource ImageSource="ms-appx:///Assets/Images/icon.png" />
@@ -97,7 +98,7 @@
9798
/>
9899
</StackPanel>
99100
<Frame Name="MainContentFrame" Grid.Row="2">
100-
<Grid Background="{StaticResource ProgressBarBorderThemeBrush}" Visibility="Visible">
101+
<Grid Background="{StaticResource ProgressBarBorderThemeBrush}" Visibility="Visible" AutomationProperties.Name="Loading UniGetUI">
101102
<Grid.ColumnDefinitions>
102103
<ColumnDefinition Width="*" />
103104
<ColumnDefinition Width="Auto" />

src/UniGetUI/Pages/MainView.xaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@
381381

382382
<controls:GridSplitter
383383
x:Name="OperationSplitter"
384+
AutomationProperties.Name="Operation list resizer"
384385
Grid.Row="1"
385386
Grid.Column="0"
386387
Height="12"
@@ -398,6 +399,7 @@
398399
</Grid.ColumnDefinitions>
399400
<HyperlinkButton
400401
Name="ExpandCollapseOpList"
402+
AutomationProperties.Name="Expand or collapse operation list"
401403
Grid.Column="1"
402404
Height="12"
403405
Padding="12,0,12,0"

src/UniGetUI/Pages/SettingsPages/GeneralPages/Updates.xaml.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ private void RefreshMinimumAgeLayout()
9898
: new CornerRadius(8);
9999
}
100100

101-
private UIElement BuildReleaseDateCompatTable()
101+
private static UIElement BuildReleaseDateCompatTable()
102102
{
103103
string yesStr = CoreTools.Translate("Yes");
104104
string noStr = CoreTools.Translate("No");

src/UniGetUI/Pages/SettingsPages/SettingsBasePage.xaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
</Grid.ColumnDefinitions>
4545
<Button
4646
Name="BackButton"
47+
AutomationProperties.Name="Back"
4748
Width="36"
4849
Height="36"
4950
Padding="6"

src/UniGetUI/Pages/SoftwarePages/AbstractPackagesPage.xaml

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
Grid.Column="0"
4949
VerticalAlignment="Center"
5050
IsChecked="{x:Bind IsChecked, Mode=TwoWay}"
51+
IsTabStop="False"
52+
AutomationProperties.AccessibilityView="Raw"
5153
/>
5254

5355
<!-- Regular or package icon -->
@@ -285,6 +287,8 @@
285287
HorizontalAlignment="Left"
286288
VerticalAlignment="Top"
287289
IsChecked="{x:Bind IsChecked, Mode=TwoWay}"
290+
IsTabStop="False"
291+
AutomationProperties.AccessibilityView="Raw"
288292
/>
289293

290294
<Button
@@ -418,6 +422,8 @@
418422
HorizontalAlignment="Left"
419423
VerticalAlignment="Top"
420424
IsChecked="{x:Bind IsChecked, Mode=TwoWay}"
425+
IsTabStop="False"
426+
AutomationProperties.AccessibilityView="Raw"
421427
/>
422428
<Button
423429
Grid.Row="0"
@@ -591,24 +597,26 @@
591597
VerticalAlignment="Center"
592598
Orientation="Horizontal"
593599
Spacing="4"
600+
AutomationProperties.Name="View modes"
601+
AutomationProperties.LocalizedControlType="grouping"
594602
>
595603
<widgets:TranslatedTextBlock VerticalAlignment="Center" Text="View mode:" />
596604
<Toolkit:Segmented
597605
x:Name="ViewModeSelector"
598606
SelectionChanged="ViewModeSelector_SelectionChanged"
599607
SelectionMode="Single"
600608
>
601-
<Toolkit:SegmentedItem x:Name="Selector_List">
609+
<Toolkit:SegmentedItem x:Name="Selector_List" AutomationProperties.Name="List view">
602610
<Toolkit:SegmentedItem.Icon>
603611
<FontIcon Glyph="&#xE8FD;" />
604612
</Toolkit:SegmentedItem.Icon>
605613
</Toolkit:SegmentedItem>
606-
<Toolkit:SegmentedItem x:Name="Selector_Grid">
614+
<Toolkit:SegmentedItem x:Name="Selector_Grid" AutomationProperties.Name="Grid view">
607615
<Toolkit:SegmentedItem.Icon>
608616
<FontIcon Glyph="&#xF168;" />
609617
</Toolkit:SegmentedItem.Icon>
610618
</Toolkit:SegmentedItem>
611-
<Toolkit:SegmentedItem x:Name="Selector_Icons">
619+
<Toolkit:SegmentedItem x:Name="Selector_Icons" AutomationProperties.Name="Icons view">
612620
<Toolkit:SegmentedItem.Icon>
613621
<FontIcon Glyph="&#xF0E2;" />
614622
</Toolkit:SegmentedItem.Icon>
@@ -641,6 +649,7 @@
641649
<StackPanel x:Name="ToggleFiltersButtonWidth" Margin="0,0,13,0" Orientation="Horizontal">
642650
<ToggleButton
643651
x:Name="ToggleFiltersButton"
652+
AutomationProperties.Name="Toggle filters panel"
644653
Grid.Column="0"
645654
Height="36"
646655
Margin="1,4"
@@ -688,6 +697,7 @@
688697
<StackPanel Grid.Column="1" Orientation="Horizontal">
689698
<Button
690699
x:Name="MainToolbarButton"
700+
AutomationProperties.Name="{x:Bind MainToolbarButtonText.Text, Mode=OneWay}"
691701
Height="36"
692702
Margin="0,0,1,0"
693703
x:FieldModifier="protected"
@@ -711,6 +721,7 @@
711721
</Button>
712722
<Button
713723
x:Name="MainToolbarButtonDropdown"
724+
AutomationProperties.Name="More actions"
714725
Width="30"
715726
Height="36"
716727
Padding="4"
@@ -728,6 +739,8 @@
728739
HorizontalAlignment="Left"
729740
x:FieldModifier="protected"
730741
DefaultLabelPosition="Right"
742+
IsDynamicOverflowEnabled="False"
743+
OverflowButtonVisibility="Collapsed"
731744
/>
732745
</Grid>
733746
</Grid>
@@ -747,6 +760,7 @@
747760
PaneClosing="FilteringPanel_PaneClosing"
748761
PanePlacement="Left"
749762
SizeChanged="FilteringPanel_SizeChanged"
763+
IsTabStop="False"
750764
>
751765
<SplitView.Pane>
752766
<ScrollViewer
@@ -759,6 +773,7 @@
759773
CornerRadius="0,8,8,0"
760774
HorizontalScrollMode="Disabled"
761775
SizeChanged="SidepanelWidth_SizeChanged"
776+
IsTabStop="False"
762777
>
763778
<Grid Name="SidePanelGrid" RowSpacing="8">
764779
<Grid.RowDefinitions>
@@ -887,7 +902,7 @@
887902
</StackPanel>
888903
</Expander.Header>
889904
<Expander.Content>
890-
<StackPanel HorizontalAlignment="Stretch" Orientation="Vertical" Spacing="0">
905+
<StackPanel HorizontalAlignment="Stretch" Orientation="Vertical" Spacing="0" AutomationProperties.Name="Filter options" AutomationProperties.LocalizedControlType="grouping">
891906
<CheckBox
892907
x:Name="InstantSearchCheckbox"
893908
x:FieldModifier="protected"
@@ -959,6 +974,7 @@
959974
>
960975
<RadioButton
961976
x:Name="QueryNameRadio"
977+
AutomationProperties.Name="Package Name"
962978
Height="30"
963979
Margin="0,-2,0,-2"
964980
x:FieldModifier="protected"
@@ -969,6 +985,7 @@
969985
</RadioButton>
970986
<RadioButton
971987
x:Name="QueryIdRadio"
988+
AutomationProperties.Name="Package ID"
972989
Height="30"
973990
Margin="0,-2,0,-2"
974991
x:FieldModifier="protected"
@@ -979,6 +996,7 @@
979996
</RadioButton>
980997
<RadioButton
981998
x:Name="QueryBothRadio"
999+
AutomationProperties.Name="Both"
9821000
Height="30"
9831001
Margin="0,-2,0,-2"
9841002
x:FieldModifier="protected"
@@ -990,6 +1008,7 @@
9901008
</RadioButton>
9911009
<RadioButton
9921010
x:Name="QueryExactMatch"
1011+
AutomationProperties.Name="Exact match"
9931012
Height="30"
9941013
Margin="0,-2,0,-2"
9951014
x:FieldModifier="protected"
@@ -1000,6 +1019,7 @@
10001019
</RadioButton>
10011020
<RadioButton
10021021
x:Name="QuerySimilarResultsRadio"
1022+
AutomationProperties.Name="Show similar packages"
10031023
Height="30"
10041024
Margin="0,-2,0,-2"
10051025
x:FieldModifier="protected"
@@ -1070,9 +1090,12 @@
10701090
HorizontalAlignment="Stretch"
10711091
BorderThickness="0"
10721092
CornerRadius="4,0,0,4"
1093+
IsTabStop="False"
1094+
AutomationProperties.AccessibilityView="Raw"
10731095
>
10741096
<CheckBox
10751097
Name="SelectAllCheckBox"
1098+
AutomationProperties.Name="Select all packages"
10761099
Margin="12,4,4,4"
10771100
Checked="SelectAllCheckBox_ValueChanged"
10781101
Unchecked="SelectAllCheckBox_ValueChanged"

src/UniGetUI/Services/UserAvatar.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ public UserAvatar()
2929
{
3030
VerticalContentAlignment = VerticalAlignment.Center;
3131
HorizontalContentAlignment = HorizontalAlignment.Center;
32+
Microsoft.UI.Xaml.Automation.AutomationProperties.SetName(this, CoreTools.Translate("User profile"));
33+
ToolTipService.SetToolTip(this, CoreTools.Translate("User profile"));
3234
_ = RefreshStatus();
3335
GitHubAuthService.AuthStatusChanged += GitHubAuthService_AuthStatusChanged;
3436
}
@@ -166,7 +168,7 @@ private PointButton GenerateLoginControl()
166168
Content = stackPanel,
167169
};
168170

169-
return new PointButton
171+
var btn = new PointButton
170172
{
171173
Margin = new Thickness(0),
172174
Padding = new Thickness(4),
@@ -176,6 +178,9 @@ private PointButton GenerateLoginControl()
176178
Content = personPicture,
177179
Flyout = flyout,
178180
};
181+
Microsoft.UI.Xaml.Automation.AutomationProperties.SetName(btn, CoreTools.Translate("User profile"));
182+
ToolTipService.SetToolTip(btn, CoreTools.Translate("User profile"));
183+
return btn;
179184
}
180185

181186
private async Task<PointButton> GenerateLogoutControl()
@@ -289,7 +294,7 @@ private async Task<PointButton> GenerateLogoutControl()
289294
Content = stackPanel,
290295
};
291296

292-
return new PointButton
297+
var btn = new PointButton
293298
{
294299
Margin = new Thickness(0),
295300
Padding = new Thickness(4),
@@ -299,6 +304,9 @@ private async Task<PointButton> GenerateLogoutControl()
299304
Content = personPicture,
300305
Flyout = flyout,
301306
};
307+
Microsoft.UI.Xaml.Automation.AutomationProperties.SetName(btn, CoreTools.Translate("User profile"));
308+
ToolTipService.SetToolTip(btn, CoreTools.Translate("User profile"));
309+
return btn;
302310
}
303311
}
304312
}

0 commit comments

Comments
 (0)