From e08c7f8149e45abac786637e44523e4a25fb139b Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Wed, 17 Jun 2026 03:47:25 +0800 Subject: [PATCH] Expose tree item selection to macOS accessibility (#21598) TreeViewItemAutomationPeer implements ISelectionItemProvider, and AvnAccessibilityElement maps setAccessibilitySelected to it, so AX clients can select a TreeViewItem. --- native/Avalonia.Native/src/OSX/automation.mm | 10 ++++ .../Peers/TreeViewItemAutomationPeer.cs | 49 ++++++++++++++++++- src/Avalonia.Native/AvnAutomationPeer.cs | 1 + src/Avalonia.Native/avn.idl | 1 + 4 files changed, 59 insertions(+), 2 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/automation.mm b/native/Avalonia.Native/src/OSX/automation.mm index 7897a76973..f355634de4 100644 --- a/native/Avalonia.Native/src/OSX/automation.mm +++ b/native/Avalonia.Native/src/OSX/automation.mm @@ -474,6 +474,12 @@ return NO; } +- (void)setAccessibilitySelected:(BOOL)accessibilitySelected +{ + if (accessibilitySelected && _peer->IsSelectionItemProvider()) + _peer->SelectionItemProvider_Select(); +} + - (BOOL)isAccessibilitySelectorAllowed:(SEL)selector { if (selector == @selector(setAccessibilityValue:)) @@ -492,6 +498,10 @@ { return _peer->IsInvokeProvider() || _peer->IsExpandCollapseProvider() || _peer->IsToggleProvider(); } + else if (selector == @selector(setAccessibilitySelected:)) + { + return _peer->IsSelectionItemProvider(); + } else if (selector == @selector(accessibilityPerformIncrement) || selector == @selector(accessibilityPerformDecrement) || selector == @selector(accessibilityMinValue) || diff --git a/src/Avalonia.Controls/Automation/Peers/TreeViewItemAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/TreeViewItemAutomationPeer.cs index 9629a75267..74bbf53ef9 100644 --- a/src/Avalonia.Controls/Automation/Peers/TreeViewItemAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/TreeViewItemAutomationPeer.cs @@ -1,14 +1,59 @@ +using Avalonia.Automation.Provider; using Avalonia.Controls; namespace Avalonia.Automation.Peers; -public class TreeViewItemAutomationPeer : ItemsControlAutomationPeer +public class TreeViewItemAutomationPeer : ItemsControlAutomationPeer, + ISelectionItemProvider { public TreeViewItemAutomationPeer(TreeViewItem owner) : base(owner) { } - + + public bool IsSelected => Owner.GetValue(TreeViewItem.IsSelectedProperty); + + public ISelectionProvider? SelectionContainer + { + get + { + if (Owner is TreeViewItem { TreeViewOwner: { } treeView }) + { + var parentPeer = GetOrCreate(treeView); + return parentPeer.GetProvider(); + } + + return null; + } + } + + public void Select() + { + EnsureEnabled(); + + if (Owner is TreeViewItem item) + { + item.TreeViewOwner?.SelectedItems.Clear(); + item.IsSelected = true; + } + } + + void ISelectionItemProvider.AddToSelection() + { + EnsureEnabled(); + + if (Owner is TreeViewItem item) + item.IsSelected = true; + } + + void ISelectionItemProvider.RemoveFromSelection() + { + EnsureEnabled(); + + if (Owner is TreeViewItem item) + item.IsSelected = false; + } + protected override AutomationControlType GetAutomationControlTypeCore() { return AutomationControlType.TreeItem; diff --git a/src/Avalonia.Native/AvnAutomationPeer.cs b/src/Avalonia.Native/AvnAutomationPeer.cs index 4a679e3ed7..b448d5cb1a 100644 --- a/src/Avalonia.Native/AvnAutomationPeer.cs +++ b/src/Avalonia.Native/AvnAutomationPeer.cs @@ -187,6 +187,7 @@ namespace Avalonia.Native public int IsSelectionItemProvider() => IsProvider(); public int SelectionItemProvider_IsSelected() => SelectionItemProvider.IsSelected.AsComBool(); + public void SelectionItemProvider_Select() => SelectionItemProvider.Select(); public IAvnAutomationPeer? ScrollProvider_GetHorizontalScrollBar() => _inner is ScrollViewerAutomationPeer scrollViewer ? Wrap(scrollViewer.GetHorizontalScrollBarPeer()) : null; diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index a1b57f7d14..5b1b97fa37 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -1318,6 +1318,7 @@ interface IAvnAutomationPeer : IUnknown bool IsSelectionItemProvider(); bool SelectionItemProvider_IsSelected(); + void SelectionItemProvider_Select(); IAvnAutomationPeer* ScrollProvider_GetHorizontalScrollBar(); IAvnAutomationPeer* ScrollProvider_GetVerticalScrollBar();