From 8add2371c6da37a69d3fe4d3ca56785b12b5e630 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 23 Mar 2021 22:55:39 +0100 Subject: [PATCH] Make root automation node come from OS. This will be needed for OSX. --- .../Automation/Peers/AutomationPeer.cs | 11 ++++++++++ .../Automation/Peers/ControlAutomationPeer.cs | 19 +++++++++++++---- .../Peers/PopupRootAutomationPeer.cs | 4 ++-- .../Automation/Peers/WindowAutomationPeer.cs | 4 ++-- .../Peers/WindowBaseAutomationPeer.cs | 4 ++-- src/Avalonia.Controls/Control.cs | 7 +++++++ .../Platform/IWindowBaseImpl.cs | 7 +++++++ src/Avalonia.Controls/Primitives/PopupRoot.cs | 4 ++-- src/Avalonia.Controls/TopLevel.cs | 2 ++ src/Avalonia.Controls/Window.cs | 4 ++-- src/Avalonia.Controls/WindowBase.cs | 21 +++++++++++++++++++ .../Remote/PreviewerWindowImpl.cs | 3 +++ src/Avalonia.DesignerSupport/Remote/Stubs.cs | 3 +++ src/Avalonia.Headless/HeadlessWindowImpl.cs | 3 +++ src/Avalonia.Native/WindowImplBase.cs | 3 +++ src/Avalonia.X11/X11Window.cs | 3 +++ .../Automation/AutomationNode.cs | 8 ++++++- .../Automation/AutomationNodeFactory.cs | 3 +-- .../Automation/RootAutomationNode.cs | 4 ++-- .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 12 +++++++---- src/Windows/Avalonia.Win32/WindowImpl.cs | 14 ++----------- 21 files changed, 108 insertions(+), 35 deletions(-) diff --git a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs index 4c517fcd71..25af340014 100644 --- a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs @@ -66,6 +66,17 @@ namespace Avalonia.Automation.Peers Node = factory.CreateNode(this); } + /// + /// Initializes a new instance of the class. + /// + /// + /// The platform automation node. + /// + protected AutomationPeer(IAutomationNode node) + { + Node = node; + } + /// /// Gets the related node in the platform UI Automation tree. /// diff --git a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs index cba5b4d79c..bd4e9dfcf4 100644 --- a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs @@ -23,10 +23,14 @@ namespace Avalonia.Automation.Peers : base(factory) { Owner = owner ?? throw new ArgumentNullException("owner"); + Initialize(); + } - owner.PropertyChanged += OwnerPropertyChanged; - var visualChildren = ((IVisual)owner).VisualChildren; - visualChildren.CollectionChanged += VisualChildrenChanged; + protected ControlAutomationPeer(IAutomationNode node, Control owner) + : base(node) + { + Owner = owner ?? throw new ArgumentNullException("owner"); + Initialize(); } public Control Owner { get; } @@ -161,11 +165,18 @@ namespace Avalonia.Automation.Peers protected override bool IsKeyboardFocusableCore() => Owner.Focusable; protected override void SetFocusCore() => Owner.Focus(); - private Rect GetBounds(TransformedBounds? bounds) + private static Rect GetBounds(TransformedBounds? bounds) { return bounds?.Bounds.TransformToAABB(bounds!.Value.Transform) ?? default; } + private void Initialize() + { + Owner.PropertyChanged += OwnerPropertyChanged; + var visualChildren = ((IVisual)Owner).VisualChildren; + visualChildren.CollectionChanged += VisualChildrenChanged; + } + private void VisualChildrenChanged(object sender, EventArgs e) => InvalidateChildren(); private void OwnerPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) diff --git a/src/Avalonia.Controls/Automation/Peers/PopupRootAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/PopupRootAutomationPeer.cs index b54a938a7f..f7e06e83eb 100644 --- a/src/Avalonia.Controls/Automation/Peers/PopupRootAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/PopupRootAutomationPeer.cs @@ -8,8 +8,8 @@ namespace Avalonia.Automation.Peers { public class PopupRootAutomationPeer : WindowBaseAutomationPeer { - public PopupRootAutomationPeer(IAutomationNodeFactory factory, PopupRoot owner) - : base(factory, owner) + public PopupRootAutomationPeer(IAutomationNode node, PopupRoot owner) + : base(node, owner) { if (owner.IsVisible) StartTrackingFocus(); diff --git a/src/Avalonia.Controls/Automation/Peers/WindowAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/WindowAutomationPeer.cs index 41778457e8..9629ae0294 100644 --- a/src/Avalonia.Controls/Automation/Peers/WindowAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/WindowAutomationPeer.cs @@ -8,8 +8,8 @@ namespace Avalonia.Automation.Peers { public class WindowAutomationPeer : WindowBaseAutomationPeer { - public WindowAutomationPeer(IAutomationNodeFactory factory, Window owner) - : base(factory, owner) + public WindowAutomationPeer(IAutomationNode node, Window owner) + : base(node, owner) { if (owner.IsVisible) StartTrackingFocus(); diff --git a/src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs index ac235b3898..f97d13c766 100644 --- a/src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs @@ -14,8 +14,8 @@ namespace Avalonia.Automation.Peers { private Control? _focus; - public WindowBaseAutomationPeer(IAutomationNodeFactory factory, WindowBase owner) - : base(factory, owner) + public WindowBaseAutomationPeer(IAutomationNode node, WindowBase owner) + : base(node, owner) { } diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 6b63ac14cc..ac95743e30 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -209,5 +209,12 @@ namespace Avalonia.Controls _automationPeer = OnCreateAutomationPeer(factory); return _automationPeer; } + + internal void SetAutomationPeer(AutomationPeer peer) + { + if (_automationPeer is object) + throw new InvalidOperationException("Automation peer is already set."); + _automationPeer = peer ?? throw new ArgumentNullException(nameof(peer)); + } } } diff --git a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs index 0d303a6666..8d18b925f8 100644 --- a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs @@ -1,4 +1,6 @@ using System; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; namespace Avalonia.Platform { @@ -44,6 +46,11 @@ namespace Avalonia.Platform /// Action Activated { get; set; } + /// + /// Gets or sets a method called when automation is started on the window. + /// + Func AutomationStarted { get; set; } + /// /// Gets the platform window handle. /// diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index 73dd68e7cb..96ceb2afcf 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -171,9 +171,9 @@ namespace Avalonia.Controls.Primitives } } - protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + protected override AutomationPeer OnCreateAutomationPeer(IAutomationNode node) { - return new PopupRootAutomationPeer(factory, this); + return new PopupRootAutomationPeer(node, this); } } } diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 7a92836ddf..cbb9506627 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -1,5 +1,7 @@ using System; using System.Reactive.Linq; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls.Platform; using Avalonia.Controls.Primitives; using Avalonia.Input; diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 7648162493..9d54f3beee 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -968,9 +968,9 @@ namespace Avalonia.Controls } } - protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + protected override AutomationPeer OnCreateAutomationPeer(IAutomationNode node) { - return new WindowAutomationPeer(factory, this); + return new WindowAutomationPeer(node, this); } } } diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index cdcb499e98..4f31dfeada 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -3,6 +3,8 @@ using System.ComponentModel; using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Layout; @@ -64,6 +66,7 @@ namespace Avalonia.Controls impl.Activated = HandleActivated; impl.Deactivated = HandleDeactivated; impl.PositionChanged = HandlePositionChanged; + impl.AutomationStarted = HandleAutomationStarted; } /// @@ -269,6 +272,17 @@ namespace Avalonia.Controls /// The actual size of the window. protected virtual Size ArrangeSetBounds(Size size) => size; + protected sealed override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + { + throw new NotSupportedException( + "Automation peer for window controls must be created by the operating system."); + } + + protected virtual AutomationPeer OnCreateAutomationPeer(IAutomationNode node) + { + throw new NotImplementedException("OnCreateAutomationPeer must be implemented in a derived class."); + } + /// /// Handles a window position change notification from /// . @@ -306,6 +320,13 @@ namespace Avalonia.Controls Deactivated?.Invoke(this, EventArgs.Empty); } + private AutomationPeer HandleAutomationStarted(IAutomationNode node) + { + var peer = OnCreateAutomationPeer(node); + SetAutomationPeer(peer); + return peer; + } + private void IsVisibleChanged(AvaloniaPropertyChangedEventArgs e) { if (!_ignoreVisibilityChange) diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index 787f44887f..1919503151 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -1,5 +1,7 @@ using System; using System.Reactive.Disposables; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls; using Avalonia.Controls.Remote.Server; using Avalonia.Input; @@ -45,6 +47,7 @@ namespace Avalonia.DesignerSupport.Remote public IPlatformHandle Handle { get; } public WindowState WindowState { get; set; } public Action WindowStateChanged { get; set; } + public Func AutomationStarted { get; set; } public Size MaxAutoSizeHint { get; } = new Size(4096, 4096); protected override void OnMessage(IAvaloniaRemoteTransportConnection transport, object obj) diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index eedfc52d9d..090d93362b 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.IO; using System.Reactive.Disposables; using System.Threading.Tasks; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Controls.Primitives.PopupPositioning; @@ -38,6 +40,7 @@ namespace Avalonia.DesignerSupport.Remote public Action PositionChanged { get; set; } public WindowState WindowState { get; set; } public Action WindowStateChanged { get; set; } + public Func AutomationStarted { get; set; } public Action TransparencyLevelChanged { get; set; } diff --git a/src/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Avalonia.Headless/HeadlessWindowImpl.cs index af522f3e36..fe3064b415 100644 --- a/src/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Avalonia.Headless/HeadlessWindowImpl.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Controls.Primitives.PopupPositioning; @@ -142,6 +144,7 @@ namespace Avalonia.Headless public IScreenImpl Screen { get; } = new HeadlessScreensStub(); public WindowState WindowState { get; set; } public Action WindowStateChanged { get; set; } + public Func AutomationStarted { get; set; } public void SetTitle(string title) { diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 71359f733d..65b1ae2120 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Controls.Platform.Surfaces; @@ -466,5 +468,6 @@ namespace Avalonia.Native public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0, 0); public IPlatformHandle Handle { get; private set; } + public Func AutomationStarted { get; set; } } } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 8f3f412578..3650bf1c79 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -6,6 +6,8 @@ using System.Linq; using System.Reactive.Disposables; using System.Text; using System.Threading.Tasks; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Controls.Primitives.PopupPositioning; @@ -1128,5 +1130,6 @@ namespace Avalonia.X11 public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0.8, 0.8); public bool NeedsManagedDecorations => false; + public Func AutomationStarted { get; set; } } } diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs index c7021e6d53..3963d0cb6c 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs @@ -56,7 +56,13 @@ namespace Avalonia.Win32.Automation Peer = peer; } - public AutomationPeer Peer { get; } + protected AutomationNode(Func peerGetter) + { + _runtimeId = new int[] { 3, GetHashCode() }; + Peer = peerGetter(this); + } + + public AutomationPeer Peer { get; protected set; } public IAutomationNodeFactory Factory => AutomationNodeFactory.Instance; public Rect BoundingRectangle diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNodeFactory.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNodeFactory.cs index 776b65adc6..a7ee0e192f 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNodeFactory.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNodeFactory.cs @@ -1,6 +1,5 @@ using Avalonia.Automation.Peers; using Avalonia.Automation.Platform; -using Avalonia.Automation.Provider; using Avalonia.Threading; #nullable enable @@ -14,7 +13,7 @@ namespace Avalonia.Win32.Automation public IAutomationNode CreateNode(AutomationPeer peer) { Dispatcher.UIThread.VerifyAccess(); - return peer is IRootProvider ? new RootAutomationNode(peer) : new AutomationNode(peer); + return new AutomationNode(peer); } } } diff --git a/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs b/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs index cb8cfae90e..dd2665a624 100644 --- a/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs +++ b/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs @@ -13,8 +13,8 @@ namespace Avalonia.Win32.Automation IRawElementProviderFragmentRoot, IRootAutomationNode { - public RootAutomationNode(AutomationPeer peer) - : base(peer) + public RootAutomationNode(Func peerGetter) + : base(peerGetter) { } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 8d3a17bcc5..31afcc368c 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -4,6 +4,7 @@ using System.Runtime.InteropServices; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Input.Raw; +using Avalonia.Win32.Automation; using Avalonia.Win32.Input; using Avalonia.Win32.Interop.Automation; using static Avalonia.Win32.Interop.UnmanagedMethods; @@ -76,7 +77,7 @@ namespace Avalonia.Win32 case WindowsMessage.WM_DESTROY: { - if (_automationProvider is object) + if (_automationNode is object) UiaCoreProviderApi.UiaReturnRawElementProvider(_hwnd, IntPtr.Zero, IntPtr.Zero, null); //Window doesn't exist anymore @@ -461,11 +462,14 @@ namespace Avalonia.Win32 case WindowsMessage.WM_GETOBJECT: if ((long)lParam == UiaRootObjectId) { - var provider = GetOrCreateAutomationProvider(); + if (_automationNode is null && AutomationStarted is object) + { + _automationNode = new RootAutomationNode(AutomationStarted); + } - if (provider is object) + if (_automationNode is object) { - var r = UiaCoreProviderApi.UiaReturnRawElementProvider(_hwnd, wParam, lParam, provider); + var r = UiaCoreProviderApi.UiaReturnRawElementProvider(_hwnd, wParam, lParam, _automationNode); return r; } } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 02fbb7a143..fa3d2665ed 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -87,7 +87,7 @@ namespace Avalonia.Win32 private POINT _maxTrackSize; private WindowImpl _parent; private ExtendClientAreaChromeHints _extendChromeHints = ExtendClientAreaChromeHints.Default; - private AutomationNode _automationProvider; + private AutomationNode _automationNode; private bool _isCloseRequested; private bool _shown; @@ -175,6 +175,7 @@ namespace Avalonia.Win32 public Action LostFocus { get; set; } public Action TransparencyLevelChanged { get; set; } + public Func AutomationStarted { get; set; } public Thickness BorderThickness { @@ -1269,17 +1270,6 @@ namespace Avalonia.Win32 /// public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0.8, 0); - internal AutomationNode GetOrCreateAutomationProvider() - { - if (_automationProvider is null) - { - var peer = ControlAutomationPeer.GetOrCreatePeer(AutomationNodeFactory.Instance, (Control)_owner); - _automationProvider = peer.Node as AutomationNode; - } - - return _automationProvider; - } - private struct SavedWindowInfo { public WindowStyles Style { get; set; }