Browse Source

Added IEmbeddedRootProvider.

For some reason, on win32 embedded `IRawElementProviderFragmentRoot`s just don't show up, so we need an interface to distinguish between "actual" root peers and "embedded" root peers. Ideally `IRootProvider` and `IEmbeddedRootProvider` would share a common interface but that would be a breaking change.
pull/12330/head
Steven Kirk 3 years ago
parent
commit
b7fcb14142
  1. 2
      src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs
  2. 33
      src/Avalonia.Controls/Automation/Provider/IEmbeddedRootProvider.cs
  3. 25
      src/Avalonia.Controls/Automation/Provider/IRootProvider.cs
  4. 69
      src/Windows/Avalonia.Win32/Automation/AutomationNode.cs
  5. 48
      src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs

2
src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs

@ -39,7 +39,7 @@ namespace Avalonia.Automation.Peers
var peer = GetOrCreate(hit);
while (peer != this && peer.GetProvider<IRootProvider>() is { } embedded)
while (peer != this && peer.GetProvider<IEmbeddedRootProvider>() is { } embedded)
{
var embeddedHit = embedded.GetPeerFromPoint(p);
if (embeddedHit is null)

33
src/Avalonia.Controls/Automation/Provider/IEmbeddedRootProvider.cs

@ -0,0 +1,33 @@
using System;
using Avalonia.Automation.Peers;
namespace Avalonia.Automation.Provider
{
/// <summary>
/// Exposure methods and properties to support UI Automation client access to the root of an
/// automation tree hosted by another UI framework.
/// </summary>
/// <remarks>
/// This interface is implemented by the <see cref="AutomationPeer"/> class, and can be used
/// to embed an automation tree from a 3rd party UI framework that wishes to use Avalonia's
/// automation support.
/// </remarks>
public interface IEmbeddedRootProvider
{
/// <summary>
/// Gets the currently focused element.
/// </summary>
AutomationPeer? GetFocus();
/// <summary>
/// Gets the element at the specified point, expressed in top-level coordinates.
/// </summary>
/// <param name="p">The point.</param>
AutomationPeer? GetPeerFromPoint(Point p);
/// <summary>
/// Raised by the automation peer when the focus changes.
/// </summary>
event EventHandler? FocusChanged;
}
}

25
src/Avalonia.Controls/Automation/Provider/IRootProvider.cs

@ -4,11 +4,36 @@ using Avalonia.Platform;
namespace Avalonia.Automation.Provider
{
/// <summary>
/// Exposes methods and properties to support UI Automation client access to the root of an
/// automation tree.
/// </summary>
/// <remarks>
/// This interface is implemented by the <see cref="AutomationPeer"/> class, and should only
/// be implemented on true root elements, such as Windows. To embed an automation tree, use
/// <see cref="IEmbeddedRootProvider"/> instead.
/// </remarks>
public interface IRootProvider
{
/// <summary>
/// Gets the platform implementation of the TopLevel for the element.
/// </summary>
ITopLevelImpl? PlatformImpl { get; }
/// <summary>
/// Gets the currently focused element.
/// </summary>
AutomationPeer? GetFocus();
/// <summary>
/// Gets the element at the specified point, expressed in top-level coordinates.
/// </summary>
/// <param name="p">The point.</param>
AutomationPeer? GetPeerFromPoint(Point p);
/// <summary>
/// Raised by the automation peer when the focus changes.
/// </summary>
event EventHandler? FocusChanged;
}
}

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

@ -54,25 +54,11 @@ namespace Avalonia.Win32.Automation
_runtimeId = new int[] { 3, GetHashCode() };
Peer = peer;
s_nodes.Add(peer, this);
peer.ChildrenChanged += Peer_ChildrenChanged;
peer.PropertyChanged += Peer_PropertyChanged;
}
private void Peer_ChildrenChanged(object? sender, EventArgs e)
{
ChildrenChanged();
}
peer.ChildrenChanged += OnPeerChildrenChanged;
peer.PropertyChanged += OnPeerPropertyChanged;
private void Peer_PropertyChanged(object? sender, AutomationPropertyChangedEventArgs e)
{
if (s_propertyMap.TryGetValue(e.Property, out var id))
{
UiaCoreProviderApi.UiaRaiseAutomationPropertyChangedEvent(
this,
(int)id,
e.OldValue as IConvertible,
e.NewValue as IConvertible);
}
if (Peer.GetProvider<AAP.IEmbeddedRootProvider>() is { } embeddedRoot)
embeddedRoot.FocusChanged += OnEmbeddedRootFocusChanged;
}
public AutomationPeer Peer { get; protected set; }
@ -95,15 +81,6 @@ namespace Avalonia.Win32.Automation
public virtual IRawElementProviderSimple? HostRawElementProvider => null;
public ProviderOptions ProviderOptions => ProviderOptions.ServerSideProvider;
public void ChildrenChanged()
{
UiaCoreProviderApi.UiaRaiseStructureChangedEvent(
this,
StructureChangeType.ChildrenInvalidated,
null,
0);
}
[return: MarshalAs(UnmanagedType.IUnknown)]
public virtual object? GetPatternProvider(int patternId)
{
@ -250,6 +227,21 @@ namespace Avalonia.Win32.Automation
throw new NotSupportedException();
}
protected void RaiseChildrenChanged()
{
UiaCoreProviderApi.UiaRaiseStructureChangedEvent(
this,
StructureChangeType.ChildrenInvalidated,
null,
0);
}
protected void RaiseFocusChanged(AutomationNode? focused)
{
UiaCoreProviderApi.UiaRaiseAutomationEvent(
focused,
(int)UiaEventId.AutomationFocusChanged);
}
private AutomationNode? GetRoot()
{
@ -267,6 +259,29 @@ namespace Avalonia.Win32.Automation
return peer is object ? GetOrCreate(peer) : null;
}
private void OnPeerChildrenChanged(object? sender, EventArgs e)
{
RaiseChildrenChanged();
}
private void OnPeerPropertyChanged(object? sender, AutomationPropertyChangedEventArgs e)
{
if (s_propertyMap.TryGetValue(e.Property, out var id))
{
UiaCoreProviderApi.UiaRaiseAutomationPropertyChangedEvent(
this,
(int)id,
e.OldValue as IConvertible,
e.NewValue as IConvertible);
}
}
private void OnEmbeddedRootFocusChanged(object? sender, EventArgs e)
{
if (Peer.GetProvider<AAP.IEmbeddedRootProvider>() is { } embeddedRoot)
RaiseFocusChanged(GetOrCreate(embeddedRoot.GetFocus()));
}
private static AutomationNode Create(AutomationPeer peer)
{
return peer.GetProvider<AAP.IRootProvider>() is object ?

48
src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs

@ -9,18 +9,14 @@ using Avalonia.Win32.Interop.Automation;
namespace Avalonia.Win32.Automation
{
[RequiresUnreferencedCode("Requires .NET COM interop")]
internal class RootAutomationNode : AutomationNode,
IRawElementProviderFragmentRoot,
IRawElementProviderAdviseEvents
internal class RootAutomationNode : AutomationNode, IRawElementProviderFragmentRoot
{
private int _raiseFocusChanged;
public RootAutomationNode(AutomationPeer peer)
: base(peer)
{
Peer = base.Peer.GetProvider<IRootProvider>() ?? throw new AvaloniaInternalException(
"Attempt to create RootAutomationNode from peer which does not implement IRootProvider.");
Peer.FocusChanged += FocusChanged;
Peer.FocusChanged += OnRootFocusChanged;
}
public override IRawElementProviderFragmentRoot? FragmentRoot => this;
@ -44,41 +40,6 @@ namespace Avalonia.Win32.Automation
return GetOrCreate(focus);
}
void IRawElementProviderAdviseEvents.AdviseEventAdded(int eventId, int[] properties)
{
switch ((UiaEventId)eventId)
{
case UiaEventId.AutomationFocusChanged:
++_raiseFocusChanged;
break;
}
}
void IRawElementProviderAdviseEvents.AdviseEventRemoved(int eventId, int[] properties)
{
switch ((UiaEventId)eventId)
{
case UiaEventId.AutomationFocusChanged:
--_raiseFocusChanged;
break;
}
}
protected void RaiseFocusChanged(AutomationNode? focused)
{
if (_raiseFocusChanged > 0)
{
UiaCoreProviderApi.UiaRaiseAutomationEvent(
focused,
(int)UiaEventId.AutomationFocusChanged);
}
}
public void FocusChanged(object? sender, EventArgs e)
{
RaiseFocusChanged(GetOrCreate(Peer.GetFocus()));
}
public Rect ToScreen(Rect rect)
{
if (WindowImpl is null)
@ -101,5 +62,10 @@ namespace Avalonia.Win32.Automation
return result;
}
}
private void OnRootFocusChanged(object? sender, EventArgs e)
{
RaiseFocusChanged(GetOrCreate(Peer.GetFocus()));
}
}
}

Loading…
Cancel
Save