mirror of
https://github.com/ChrisTitusTech/winutil.git
synced 2026-08-10 18:11:16 +10:00
Fix screen reader button accessibility and category navigation (#4944)
* Fix UI automation peer wrapping and category focusability * Add accessibility name to SettingsButton * Avoid nesting ScrollViewer in generated panels with outer viewers Inspect targetGrid and its parent hierarchy in Invoke-WPFUIElements to avoid adding an inner ScrollViewer when an outer ScrollViewer already exists.
This commit is contained in:
@@ -90,18 +90,25 @@ function Find-TweaksByNameOrDescription {
|
||||
}
|
||||
|
||||
if ($dockPanel -is [Windows.Controls.DockPanel]) {
|
||||
$itemsControl = $null
|
||||
$itemsControl = $dockPanel.Children | Where-Object { $_ -is [Windows.Controls.ItemsControl] } | Select-Object -First 1
|
||||
$container = $dockPanel.Children | Where-Object { $_ -is [Windows.Controls.ItemsControl] -or $_ -is [Windows.Controls.StackPanel] -or $_ -is [Windows.Controls.ScrollViewer] -or $_.GetType().Name -eq "ItemsControl" } | Select-Object -First 1
|
||||
|
||||
if ($null -ne $itemsControl) {
|
||||
if ($null -ne $container) {
|
||||
$targetPanel = if ($container.PSObject.Properties['Content'] -and $null -ne $container.Content) { $container.Content } else { $container }
|
||||
$items = $null
|
||||
if ($targetPanel -is [Windows.Controls.ItemsControl] -or $targetPanel.GetType().Name -eq "ItemsControl") {
|
||||
$items = $targetPanel.Items
|
||||
}
|
||||
else {
|
||||
$items = $targetPanel.Children
|
||||
}
|
||||
# Show all items in the category
|
||||
foreach ($item in $itemsControl.Items) {
|
||||
foreach ($item in $items) {
|
||||
if ($null -ne $item) {
|
||||
# Check if it's a category label (first Label in the ItemsControl)
|
||||
if ($item -is [Windows.Controls.Label]) {
|
||||
# Check if it's a category label (first Label in the container)
|
||||
if ($item -is [Windows.Controls.Label] -or $item.GetType().Name -eq "Label") {
|
||||
$item.Visibility = [Windows.Visibility]::Visible
|
||||
}
|
||||
elseif ($item -is [Windows.Controls.DockPanel] -or $item -is [Windows.Controls.StackPanel]) {
|
||||
elseif ($item -is [Windows.Controls.DockPanel] -or $item -is [Windows.Controls.StackPanel] -or $item.GetType().Name -eq "DockPanel" -or $item.GetType().Name -eq "StackPanel") {
|
||||
# Show all checkbox containers
|
||||
$item.Visibility = [Windows.Visibility]::Visible
|
||||
}
|
||||
@@ -143,16 +150,21 @@ function Find-TweaksByNameOrDescription {
|
||||
}
|
||||
|
||||
if ($dockPanel -is [Windows.Controls.DockPanel]) {
|
||||
$itemsControl = $null
|
||||
$itemsControl = $dockPanel.Children | Where-Object { $_ -is [Windows.Controls.ItemsControl] } | Select-Object -First 1
|
||||
$container = $dockPanel.Children | Where-Object { $_ -is [Windows.Controls.ItemsControl] -or $_ -is [Windows.Controls.StackPanel] -or $_ -is [Windows.Controls.ScrollViewer] -or $_.GetType().Name -eq "ItemsControl" } | Select-Object -First 1
|
||||
|
||||
if ($null -ne $itemsControl) {
|
||||
if ($null -ne $container) {
|
||||
$categoryLabel = $null
|
||||
|
||||
# Process all items (checkboxes, labels, panels) in the ItemsControl
|
||||
for ($i = 0; $i -lt $itemsControl.Items.Count; $i++) {
|
||||
$item = $itemsControl.Items[$i]
|
||||
|
||||
$targetPanel = if ($container.PSObject.Properties['Content'] -and $null -ne $container.Content) { $container.Content } else { $container }
|
||||
$items = $null
|
||||
if ($targetPanel -is [Windows.Controls.ItemsControl] -or $targetPanel.GetType().Name -eq "ItemsControl") {
|
||||
$items = $targetPanel.Items
|
||||
}
|
||||
else {
|
||||
$items = $targetPanel.Children
|
||||
}
|
||||
# Process all items (checkboxes, labels, panels) in the container
|
||||
foreach ($item in $items) {
|
||||
if ($null -eq $item) {
|
||||
continue
|
||||
}
|
||||
@@ -161,7 +173,7 @@ function Find-TweaksByNameOrDescription {
|
||||
# Check if this is a category label (usually first Label)
|
||||
# ------------------------------------------------------------
|
||||
|
||||
if ($item -is [Windows.Controls.Label]) {
|
||||
if ($item -is [Windows.Controls.Label] -or $item.GetType().Name -eq "Label") {
|
||||
$categoryLabel = $item
|
||||
# Initially hide category label; show it only if matches found
|
||||
$item.Visibility = [Windows.Visibility]::Collapsed
|
||||
@@ -171,13 +183,13 @@ function Find-TweaksByNameOrDescription {
|
||||
# Check if this is a DockPanel containing a tweak checkbox
|
||||
# ------------------------------------------------------------
|
||||
|
||||
elseif ($item -is [Windows.Controls.DockPanel]) {
|
||||
elseif ($item -is [Windows.Controls.DockPanel] -or $item.GetType().Name -eq "DockPanel") {
|
||||
$checkbox = $null
|
||||
$label = $null
|
||||
|
||||
# Safely extract checkbox and label
|
||||
$checkbox = $item.Children | Where-Object { $_ -is [Windows.Controls.CheckBox] } | Select-Object -First 1
|
||||
$label = $item.Children | Where-Object { $_ -is [Windows.Controls.Label] } | Select-Object -First 1
|
||||
$checkbox = $item.Children | Where-Object { $_ -is [Windows.Controls.CheckBox] -or $_.GetType().Name -eq "CheckBox" } | Select-Object -First 1
|
||||
$label = $item.Children | Where-Object { $_ -is [Windows.Controls.Label] -or $_.GetType().Name -eq "Label" } | Select-Object -First 1
|
||||
|
||||
# Check if tweak matches search criteria
|
||||
$itemMatches = $false
|
||||
@@ -221,9 +233,9 @@ function Find-TweaksByNameOrDescription {
|
||||
# Check if this is a StackPanel containing a tweak checkbox
|
||||
# ------------------------------------------------------------
|
||||
|
||||
elseif ($item -is [Windows.Controls.StackPanel]) {
|
||||
elseif ($item -is [Windows.Controls.StackPanel] -or $item.GetType().Name -eq "StackPanel") {
|
||||
$checkbox = $null
|
||||
$checkbox = $item.Children | Where-Object { $_ -is [Windows.Controls.CheckBox] } | Select-Object -First 1
|
||||
$checkbox = $item.Children | Where-Object { $_ -is [Windows.Controls.CheckBox] -or $_.GetType().Name -eq "CheckBox" } | Select-Object -First 1
|
||||
|
||||
$itemMatches = $false
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ function Invoke-WPFUIElements {
|
||||
# Add ColumnDefinitions to the target Grid
|
||||
for ($i = 0; $i -lt $columncount; $i++) {
|
||||
$colDef = New-Object Windows.Controls.ColumnDefinition
|
||||
$colDef.Width = New-Object Windows.GridLength(1, [Windows.GridUnitType]::Star)
|
||||
$colDef.Width = New-Object System.Windows.GridLength([double]1, [System.Windows.GridUnitType]::Star)
|
||||
$targetGrid.ColumnDefinitions.Add($colDef) | Out-Null
|
||||
}
|
||||
|
||||
@@ -113,36 +113,55 @@ function Invoke-WPFUIElements {
|
||||
$dockPanelContainer = New-Object Windows.Controls.DockPanel
|
||||
$border.Child = $dockPanelContainer
|
||||
|
||||
# Create an ItemsControl for application content
|
||||
$itemsControl = New-Object Windows.Controls.ItemsControl
|
||||
$itemsControl.HorizontalAlignment = 'Stretch'
|
||||
$itemsControl.VerticalAlignment = 'Stretch'
|
||||
# Create a StackPanel for application content controls
|
||||
$stackPanelContainer = New-Object Windows.Controls.StackPanel
|
||||
$stackPanelContainer.HorizontalAlignment = 'Stretch'
|
||||
$stackPanelContainer.VerticalAlignment = 'Stretch'
|
||||
|
||||
# Set the ItemsPanel to a VirtualizingStackPanel
|
||||
$itemsPanelTemplate = New-Object Windows.Controls.ItemsPanelTemplate
|
||||
$factory = New-Object Windows.FrameworkElementFactory ([Windows.Controls.VirtualizingStackPanel])
|
||||
$itemsPanelTemplate.VisualTree = $factory
|
||||
$itemsControl.ItemsPanel = $itemsPanelTemplate
|
||||
# Check if the target grid (or any ancestor) is already inside a ScrollViewer
|
||||
$hasOuterScrollViewer = $false
|
||||
$currentElement = $targetGrid
|
||||
while ($null -ne $currentElement) {
|
||||
if ($currentElement -is [System.Windows.Controls.ScrollViewer] -or $currentElement.GetType().Name -eq "ScrollViewer") {
|
||||
$hasOuterScrollViewer = $true
|
||||
break
|
||||
}
|
||||
$currentElement = $currentElement.Parent
|
||||
}
|
||||
|
||||
# Set virtualization properties
|
||||
$itemsControl.SetValue([Windows.Controls.VirtualizingStackPanel]::IsVirtualizingProperty, $true)
|
||||
$itemsControl.SetValue([Windows.Controls.VirtualizingStackPanel]::VirtualizationModeProperty, [Windows.Controls.VirtualizationMode]::Recycling)
|
||||
if ($hasOuterScrollViewer) {
|
||||
# Add StackPanel directly to DockPanel without nesting a ScrollViewer
|
||||
[Windows.Controls.DockPanel]::SetDock($stackPanelContainer, [Windows.Controls.Dock]::Bottom)
|
||||
$dockPanelContainer.Children.Add($stackPanelContainer) | Out-Null
|
||||
}
|
||||
else {
|
||||
# Create a ScrollViewer for targets that do not already have an outer ScrollViewer
|
||||
$scrollViewer = New-Object Windows.Controls.ScrollViewer
|
||||
$scrollViewer.VerticalScrollBarVisibility = "Auto"
|
||||
$scrollViewer.HorizontalScrollBarVisibility = "Disabled"
|
||||
$scrollViewer.HorizontalAlignment = 'Stretch'
|
||||
$scrollViewer.VerticalAlignment = 'Stretch'
|
||||
$scrollViewer.Content = $stackPanelContainer
|
||||
|
||||
# Add the ItemsControl directly to the DockPanel
|
||||
[Windows.Controls.DockPanel]::SetDock($itemsControl, [Windows.Controls.Dock]::Bottom)
|
||||
$dockPanelContainer.Children.Add($itemsControl) | Out-Null
|
||||
[Windows.Controls.DockPanel]::SetDock($scrollViewer, [Windows.Controls.Dock]::Bottom)
|
||||
$dockPanelContainer.Children.Add($scrollViewer) | Out-Null
|
||||
}
|
||||
$panelcount++
|
||||
|
||||
# Now proceed with adding category labels and entries to $itemsControl
|
||||
# Now proceed with adding category labels and entries to $stackPanelContainer
|
||||
foreach ($category in ($organizedData[$panelKey].Keys | Sort-Object)) {
|
||||
$count++
|
||||
|
||||
$label = New-Object Windows.Controls.Label
|
||||
$label.Content = $category -replace ".*__", ""
|
||||
$categoryCleanName = $category -replace ".*__", ""
|
||||
$label.Content = $categoryCleanName
|
||||
$label.Focusable = $true
|
||||
$label.IsTabStop = $true
|
||||
[System.Windows.Automation.AutomationProperties]::SetName($label, $categoryCleanName)
|
||||
$label.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "HeaderFontSize")
|
||||
$label.SetResourceReference([Windows.Controls.Control]::FontFamilyProperty, "HeaderFontFamily")
|
||||
$label.UseLayoutRounding = $true
|
||||
$itemsControl.Items.Add($label) | Out-Null
|
||||
$stackPanelContainer.Children.Add($label) | Out-Null
|
||||
$sync[$category] = $label
|
||||
|
||||
# Sort entries by type (checkboxes first, then buttons, then comboboxes, notes last) and then alphabetically by Content
|
||||
@@ -177,7 +196,7 @@ function Invoke-WPFUIElements {
|
||||
$label.SetResourceReference([Windows.Controls.Control]::ForegroundProperty, "MainForegroundColor")
|
||||
$label.UseLayoutRounding = $true
|
||||
$dockPanel.Children.Add($label) | Out-Null
|
||||
$itemsControl.Items.Add($dockPanel) | Out-Null
|
||||
$stackPanelContainer.Children.Add($dockPanel) | Out-Null
|
||||
|
||||
$sync[$entryInfo.Name] = $checkBox
|
||||
$sync[$entryInfo.Name].IsChecked = (Get-WinUtilToggleStatus $entryInfo.Name)
|
||||
@@ -215,7 +234,7 @@ function Invoke-WPFUIElements {
|
||||
contentOff = if ($entryInfo.Content.Count -ge 2) { $entryInfo.Content[1] } else { $contentOn }
|
||||
}
|
||||
|
||||
$itemsControl.Items.Add($toggleButton) | Out-Null
|
||||
$stackPanelContainer.Children.Add($toggleButton) | Out-Null
|
||||
|
||||
$sync[$entryInfo.Name] = $toggleButton
|
||||
|
||||
@@ -295,7 +314,7 @@ function Invoke-WPFUIElements {
|
||||
}
|
||||
|
||||
$horizontalStackPanel.Children.Add($comboBox) | Out-Null
|
||||
$itemsControl.Items.Add($horizontalStackPanel) | Out-Null
|
||||
$stackPanelContainer.Children.Add($horizontalStackPanel) | Out-Null
|
||||
|
||||
if ($entryInfo.Registry -and @($entryInfo.Registry)[0].Values) {
|
||||
try {
|
||||
@@ -387,7 +406,7 @@ function Invoke-WPFUIElements {
|
||||
$button.Width = [math]::Max($baseWidth, 350)
|
||||
}
|
||||
[System.Windows.Automation.AutomationProperties]::SetName($button, $entryInfo.Content)
|
||||
$itemsControl.Items.Add($button) | Out-Null
|
||||
$stackPanelContainer.Children.Add($button) | Out-Null
|
||||
|
||||
$sync[$entryInfo.Name] = $button
|
||||
|
||||
@@ -414,7 +433,7 @@ function Invoke-WPFUIElements {
|
||||
$radioButtonGroups[$entryInfo.GroupName] = $groupStackPanel
|
||||
|
||||
# Add the group container to the ItemsControl
|
||||
$itemsControl.Items.Add($groupStackPanel) | Out-Null
|
||||
$stackPanelContainer.Children.Add($groupStackPanel) | Out-Null
|
||||
}
|
||||
else {
|
||||
# Retrieve the existing group container
|
||||
@@ -459,7 +478,7 @@ function Invoke-WPFUIElements {
|
||||
$textBlock.Inlines.Add($bulletBadge)
|
||||
$textBlock.Inlines.Add($textRun)
|
||||
|
||||
$itemsControl.Items.Add($textBlock) | Out-Null
|
||||
$stackPanelContainer.Children.Add($textBlock) | Out-Null
|
||||
}
|
||||
|
||||
default {
|
||||
@@ -519,7 +538,7 @@ function Invoke-WPFUIElements {
|
||||
$sync[$textBlock.Name] = $textBlock
|
||||
}
|
||||
|
||||
$itemsControl.Items.Add($horizontalStackPanel) | Out-Null
|
||||
$stackPanelContainer.Children.Add($horizontalStackPanel) | Out-Null
|
||||
$sync[$entryInfo.Name] = $checkBox
|
||||
|
||||
$sync[$entryInfo.Name].Add_Checked({
|
||||
|
||||
@@ -138,4 +138,11 @@ Describe "Startup lazy tab wiring" {
|
||||
$rendererScript | Should -Match '(?s)if \(\$entryInfo\.Link\).*\$textBlock\.Add_MouseUp\(\{.*Start-Process \$Sender\.ToolTip -ErrorAction Stop'
|
||||
$mainScript | Should -Not -Match '\.Name\.EndsWith\("Link"\)'
|
||||
}
|
||||
|
||||
It "checks for an existing outer ScrollViewer before nesting an inner ScrollViewer" {
|
||||
$rendererScript = Get-Content -Path (Join-Path $script:repoRoot "functions\public\Invoke-WPFUIElements.ps1") -Raw
|
||||
|
||||
$rendererScript | Should -Match '\$hasOuterScrollViewer'
|
||||
$rendererScript | Should -Match 'if\s*\(\$hasOuterScrollViewer\)'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1257,6 +1257,8 @@
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,2,0"
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
ToolTip="Settings"
|
||||
AutomationProperties.Name="Settings"
|
||||
Content=""/>
|
||||
<Popup Name="SettingsPopup"
|
||||
IsOpen="False"
|
||||
|
||||
Reference in New Issue
Block a user