Browse Source

Make root automation node come from OS.

This will be needed for OSX.
ui-automation-test
Steven Kirk 5 years ago
parent
commit
8add2371c6
  1. 11
      src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs
  2. 19
      src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs
  3. 4
      src/Avalonia.Controls/Automation/Peers/PopupRootAutomationPeer.cs
  4. 4
      src/Avalonia.Controls/Automation/Peers/WindowAutomationPeer.cs
  5. 4
      src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs
  6. 7
      src/Avalonia.Controls/Control.cs
  7. 7
      src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
  8. 4
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  9. 2
      src/Avalonia.Controls/TopLevel.cs
  10. 4
      src/Avalonia.Controls/Window.cs
  11. 21
      src/Avalonia.Controls/WindowBase.cs
  12. 3
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  13. 3
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  14. 3
      src/Avalonia.Headless/HeadlessWindowImpl.cs
  15. 3
      src/Avalonia.Native/WindowImplBase.cs
  16. 3
      src/Avalonia.X11/X11Window.cs
  17. 8
      src/Windows/Avalonia.Win32/Automation/AutomationNode.cs
  18. 3
      src/Windows/Avalonia.Win32/Automation/AutomationNodeFactory.cs
  19. 4
      src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs
  20. 12
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  21. 14
      src/Windows/Avalonia.Win32/WindowImpl.cs

11
src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs

@ -66,6 +66,17 @@ namespace Avalonia.Automation.Peers
Node = factory.CreateNode(this);
}
/// <summary>
/// Initializes a new instance of the <see cref="AutomationPeer"/> class.
/// </summary>
/// <param name="node">
/// The platform automation node.
/// </param>
protected AutomationPeer(IAutomationNode node)
{
Node = node;
}
/// <summary>
/// Gets the related node in the platform UI Automation tree.
/// </summary>

19
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)

4
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();

4
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();

4
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)
{
}

7
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));
}
}
}

7
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
/// </summary>
Action Activated { get; set; }
/// <summary>
/// Gets or sets a method called when automation is started on the window.
/// </summary>
Func<IAutomationNode, AutomationPeer> AutomationStarted { get; set; }
/// <summary>
/// Gets the platform window handle.
/// </summary>

4
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);
}
}
}

2
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;

4
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);
}
}
}

21
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;
}
/// <summary>
@ -269,6 +272,17 @@ namespace Avalonia.Controls
/// <returns>The actual size of the window.</returns>
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.");
}
/// <summary>
/// Handles a window position change notification from
/// <see cref="IWindowBaseImpl.PositionChanged"/>.
@ -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)

3
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<WindowState> WindowStateChanged { get; set; }
public Func<IAutomationNode, AutomationPeer> AutomationStarted { get; set; }
public Size MaxAutoSizeHint { get; } = new Size(4096, 4096);
protected override void OnMessage(IAvaloniaRemoteTransportConnection transport, object obj)

3
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<PixelPoint> PositionChanged { get; set; }
public WindowState WindowState { get; set; }
public Action<WindowState> WindowStateChanged { get; set; }
public Func<IAutomationNode, AutomationPeer> AutomationStarted { get; set; }
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }

3
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<WindowState> WindowStateChanged { get; set; }
public Func<IAutomationNode, AutomationPeer> AutomationStarted { get; set; }
public void SetTitle(string title)
{

3
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<IAutomationNode, AutomationPeer> AutomationStarted { get; set; }
}
}

3
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<IAutomationNode, AutomationPeer> AutomationStarted { get; set; }
}
}

8
src/Windows/Avalonia.Win32/Automation/AutomationNode.cs

@ -56,7 +56,13 @@ namespace Avalonia.Win32.Automation
Peer = peer;
}
public AutomationPeer Peer { get; }
protected AutomationNode(Func<IAutomationNode, AutomationPeer> peerGetter)
{
_runtimeId = new int[] { 3, GetHashCode() };
Peer = peerGetter(this);
}
public AutomationPeer Peer { get; protected set; }
public IAutomationNodeFactory Factory => AutomationNodeFactory.Instance;
public Rect BoundingRectangle

3
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);
}
}
}

4
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<IAutomationNode, AutomationPeer> peerGetter)
: base(peerGetter)
{
}

12
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;
}
}

14
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<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
public Func<IAutomationNode, AutomationPeer> AutomationStarted { get; set; }
public Thickness BorderThickness
{
@ -1269,17 +1270,6 @@ namespace Avalonia.Win32
/// <inheritdoc/>
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; }

Loading…
Cancel
Save