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); 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); var embeddedHit = embedded.GetPeerFromPoint(p);
if (embeddedHit is null) 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 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 public interface IRootProvider
{ {
/// <summary>
/// Gets the platform implementation of the TopLevel for the element.
/// </summary>
ITopLevelImpl? PlatformImpl { get; } ITopLevelImpl? PlatformImpl { get; }
/// <summary>
/// Gets the currently focused element.
/// </summary>
AutomationPeer? GetFocus(); 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); AutomationPeer? GetPeerFromPoint(Point p);
/// <summary>
/// Raised by the automation peer when the focus changes.
/// </summary>
event EventHandler? FocusChanged; event EventHandler? FocusChanged;
} }
} }

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

@ -54,25 +54,11 @@ namespace Avalonia.Win32.Automation
_runtimeId = new int[] { 3, GetHashCode() }; _runtimeId = new int[] { 3, GetHashCode() };
Peer = peer; Peer = peer;
s_nodes.Add(peer, this); s_nodes.Add(peer, this);
peer.ChildrenChanged += Peer_ChildrenChanged; peer.ChildrenChanged += OnPeerChildrenChanged;
peer.PropertyChanged += Peer_PropertyChanged; peer.PropertyChanged += OnPeerPropertyChanged;
}
private void Peer_ChildrenChanged(object? sender, EventArgs e)
{
ChildrenChanged();
}
private void Peer_PropertyChanged(object? sender, AutomationPropertyChangedEventArgs e) if (Peer.GetProvider<AAP.IEmbeddedRootProvider>() is { } embeddedRoot)
{ embeddedRoot.FocusChanged += OnEmbeddedRootFocusChanged;
if (s_propertyMap.TryGetValue(e.Property, out var id))
{
UiaCoreProviderApi.UiaRaiseAutomationPropertyChangedEvent(
this,
(int)id,
e.OldValue as IConvertible,
e.NewValue as IConvertible);
}
} }
public AutomationPeer Peer { get; protected set; } public AutomationPeer Peer { get; protected set; }
@ -95,15 +81,6 @@ namespace Avalonia.Win32.Automation
public virtual IRawElementProviderSimple? HostRawElementProvider => null; public virtual IRawElementProviderSimple? HostRawElementProvider => null;
public ProviderOptions ProviderOptions => ProviderOptions.ServerSideProvider; public ProviderOptions ProviderOptions => ProviderOptions.ServerSideProvider;
public void ChildrenChanged()
{
UiaCoreProviderApi.UiaRaiseStructureChangedEvent(
this,
StructureChangeType.ChildrenInvalidated,
null,
0);
}
[return: MarshalAs(UnmanagedType.IUnknown)] [return: MarshalAs(UnmanagedType.IUnknown)]
public virtual object? GetPatternProvider(int patternId) public virtual object? GetPatternProvider(int patternId)
{ {
@ -250,6 +227,21 @@ namespace Avalonia.Win32.Automation
throw new NotSupportedException(); 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() private AutomationNode? GetRoot()
{ {
@ -267,6 +259,29 @@ namespace Avalonia.Win32.Automation
return peer is object ? GetOrCreate(peer) : null; 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) private static AutomationNode Create(AutomationPeer peer)
{ {
return peer.GetProvider<AAP.IRootProvider>() is object ? 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 namespace Avalonia.Win32.Automation
{ {
[RequiresUnreferencedCode("Requires .NET COM interop")] [RequiresUnreferencedCode("Requires .NET COM interop")]
internal class RootAutomationNode : AutomationNode, internal class RootAutomationNode : AutomationNode, IRawElementProviderFragmentRoot
IRawElementProviderFragmentRoot,
IRawElementProviderAdviseEvents
{ {
private int _raiseFocusChanged;
public RootAutomationNode(AutomationPeer peer) public RootAutomationNode(AutomationPeer peer)
: base(peer) : base(peer)
{ {
Peer = base.Peer.GetProvider<IRootProvider>() ?? throw new AvaloniaInternalException( Peer = base.Peer.GetProvider<IRootProvider>() ?? throw new AvaloniaInternalException(
"Attempt to create RootAutomationNode from peer which does not implement IRootProvider."); "Attempt to create RootAutomationNode from peer which does not implement IRootProvider.");
Peer.FocusChanged += FocusChanged; Peer.FocusChanged += OnRootFocusChanged;
} }
public override IRawElementProviderFragmentRoot? FragmentRoot => this; public override IRawElementProviderFragmentRoot? FragmentRoot => this;
@ -44,41 +40,6 @@ namespace Avalonia.Win32.Automation
return GetOrCreate(focus); 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) public Rect ToScreen(Rect rect)
{ {
if (WindowImpl is null) if (WindowImpl is null)
@ -101,5 +62,10 @@ namespace Avalonia.Win32.Automation
return result; return result;
} }
} }
private void OnRootFocusChanged(object? sender, EventArgs e)
{
RaiseFocusChanged(GetOrCreate(Peer.GetFocus()));
}
} }
} }

Loading…
Cancel
Save