Browse Source

Fix empty automation bounds for custom root providers (#21730)

* Add AutomationPeer.ToScreen so each peer converts its own bounding rectangle to screen coordinates, fixing empty UIA bounds for root providers that are not ControlAutomationPeers.

* Update AutomationPeer.cs

* Update AutomationPeer.cs
pull/21736/head
Jumar Macato 3 months ago
committed by GitHub
parent
commit
a6dbeb7d9c
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 13
      src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs
  2. 7
      src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs
  3. 14
      src/Windows/Avalonia.Win32.Automation/AutomationNode.cs
  4. 25
      tests/Avalonia.Controls.UnitTests/Automation/ControlAutomationPeerTests.cs

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

@ -420,6 +420,17 @@ namespace Avalonia.Automation.Peers
[PrivateApi]
public AutomationPeer? GetAutomationRoot() => GetAutomationRootCore();
/// <summary>
/// Converts a rectangle in the peer's coordinate space, as returned by
/// <see cref="GetBoundingRectangle"/>, to screen coordinates; returns null if the peer is
/// not currently hosted in a rendered visual tree.
/// </summary>
/// <param name="rect">
/// A rectangle in the peer's own coordinate space, as returned by
/// <see cref="GetBoundingRectangle"/>.
/// </param>
public Rect? ToScreen(Rect rect) => ToScreenCore(rect);
/// <summary>
/// Gets a value that indicates whether the element that is associated with this automation
/// peer currently has keyboard focus.
@ -669,6 +680,8 @@ namespace Avalonia.Automation.Peers
private protected virtual AutomationPeer? GetVisualRootCore() => GetAutomationRootCore();
private protected virtual Rect? ToScreenCore(Rect rect) => null;
protected virtual bool IsContentElementOverrideCore()
{

7
src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs

@ -155,6 +155,13 @@ namespace Avalonia.Automation.Peers
return null;
}
private protected override Rect? ToScreenCore(Rect rect)
{
if (Owner?.PresentationSource?.RootVisual is not { } root)
return null;
return new PixelRect(root.PointToScreen(rect.TopLeft), root.PointToScreen(rect.BottomRight)).ToRect(1);
}
/// <summary>
/// Invalidates the peer's children and causes a re-read from <see cref="GetChildrenCore"/>.
/// </summary>

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

@ -11,7 +11,6 @@ using Avalonia.Automation;
using Avalonia.Automation.Peers;
using Avalonia.Controls.Automation.Peers;
using Avalonia.Threading;
using Avalonia.VisualTree;
using Avalonia.Win32.Automation.Interop;
using AAP = Avalonia.Automation.Provider;
using UIA = Avalonia.Win32.Automation.Interop;
@ -76,18 +75,7 @@ namespace Avalonia.Win32.Automation
public virtual Rect GetBoundingRectangle()
{
return InvokeSync(() =>
{
if (Peer.GetVisualRoot() is ControlAutomationPeer root &&
root.Owner.GetPresentationSource() is not null)
{
var originalRect = Peer.GetBoundingRectangle();
return new PixelRect(root.Owner.PointToScreen(originalRect.TopLeft),
root.Owner.PointToScreen(originalRect.BottomRight)).ToRect(1);
}
return default;
});
return InvokeSync(() => Peer.ToScreen(Peer.GetBoundingRectangle()) ?? default);
}
public virtual IRawElementProviderFragmentRoot? GetFragmentRoot()

25
tests/Avalonia.Controls.UnitTests/Automation/ControlAutomationPeerTests.cs

@ -232,6 +232,31 @@ namespace Avalonia.Controls.UnitTests.Automation
}
}
public class ToScreen : ScopedTestBase
{
[Fact]
public void Converts_Rect_When_Attached_To_Rooted_Tree()
{
var border = new Border();
var root = new TestRoot(border);
var peer = CreatePeer(border);
Assert.Equal(new Rect(10, 20, 30, 40), peer.ToScreen(new Rect(10, 20, 30, 40)));
}
[Fact]
public void Returns_Null_When_Detached()
{
var border = new Border();
var root = new TestRoot(border);
var peer = CreatePeer(border);
root.Child = null;
Assert.Null(peer.ToScreen(new Rect(10, 20, 30, 40)));
}
}
private static AutomationPeer CreatePeer(Control control)
{
return ControlAutomationPeer.CreatePeerForElement(control);

Loading…
Cancel
Save