A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

213 lines
6.7 KiB

using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Automation.Platform;
using Avalonia.Controls;
using Avalonia.VisualTree;
#nullable enable
namespace Avalonia.Automation.Peers
{
/// <summary>
/// An automation peer which represents a <see cref="Control"/> element.
/// </summary>
public class ControlAutomationPeer : AutomationPeer
{
private IReadOnlyList<AutomationPeer>? _children;
private bool _childrenValid;
private AutomationPeer? _parent;
private bool _parentValid;
public ControlAutomationPeer(IAutomationNodeFactory factory, Control owner)
: base(factory)
{
Owner = owner ?? throw new ArgumentNullException("owner");
owner.PropertyChanged += OwnerPropertyChanged;
var visualChildren = ((IVisual)owner).VisualChildren;
visualChildren.CollectionChanged += VisualChildrenChanged;
}
public Control Owner { get; }
public static AutomationPeer GetOrCreatePeer(IAutomationNodeFactory factory, Control element)
{
element = element ?? throw new ArgumentNullException("element");
return element.GetOrCreateAutomationPeer(factory);
}
public AutomationPeer GetOrCreatePeer(Control element)
{
return element == Owner ? this : GetOrCreatePeer(Node.Factory, element);
}
protected override void BringIntoViewCore() => Owner.BringIntoView();
protected override IReadOnlyList<AutomationPeer> GetOrCreateChildrenCore()
{
var children = _children ?? Array.Empty<AutomationPeer>();
if (_childrenValid)
return children;
var newChildren = GetChildrenCore() ?? Array.Empty<AutomationPeer>();
foreach (var peer in children.Except(newChildren))
peer.TrySetParent(null);
foreach (var peer in newChildren)
peer.TrySetParent(this);
_childrenValid = true;
return _children = newChildren;
}
protected virtual IReadOnlyList<AutomationPeer>? GetChildrenCore()
{
var children = ((IVisual)Owner).VisualChildren;
if (children.Count == 0)
return null;
var result = new List<AutomationPeer>();
foreach (var child in children)
{
if (child is Control c && c.IsVisible)
{
result.Add(GetOrCreatePeer(c));
}
}
return result;
}
protected override AutomationPeer? GetLabeledByCore()
{
var label = AutomationProperties.GetLabeledBy(Owner);
return label is Control c ? GetOrCreatePeer(c) : null;
}
protected override string? GetNameCore()
{
var result = AutomationProperties.GetName(Owner);
if (string.IsNullOrWhiteSpace(result) && GetLabeledBy() is AutomationPeer labeledBy)
{
return labeledBy.GetName();
}
return null;
}
protected override AutomationPeer? GetParentCore()
{
EnsureConnected();
return _parent;
}
/// <summary>
/// Invalidates the peer's children and causes a re-read from <see cref="GetChildrenCore"/>.
/// </summary>
protected void InvalidateChildren()
{
_childrenValid = false;
Node!.ChildrenChanged();
}
/// <summary>
/// Invalidates the peer's parent.
/// </summary>
protected void InvalidateParent()
{
_parent = null;
_parentValid = false;
}
protected override bool ShowContextMenuCore()
{
var c = Owner;
while (c is object)
{
if (c.ContextMenu is object)
{
c.ContextMenu.Open(c);
return true;
}
c = c.Parent as Control;
}
return false;
}
protected internal override bool TrySetParent(AutomationPeer? parent)
{
_parent = parent;
return true;
}
protected override string? GetAutomationIdCore() => AutomationProperties.GetAutomationId(Owner) ?? Owner.Name;
protected override Rect GetBoundingRectangleCore() => GetBounds(Owner.TransformedBounds);
protected override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.Custom;
protected override string GetClassNameCore() => Owner.GetType().Name;
protected override bool HasKeyboardFocusCore() => Owner.IsFocused;
protected override bool IsContentElementCore() => true;
protected override bool IsControlElementCore() => true;
protected override bool IsEnabledCore() => Owner.IsEnabled;
protected override bool IsKeyboardFocusableCore() => Owner.Focusable;
protected override void SetFocusCore() => Owner.Focus();
private Rect GetBounds(TransformedBounds? bounds)
{
return bounds?.Bounds.TransformToAABB(bounds!.Value.Transform) ?? default;
}
private void VisualChildrenChanged(object sender, EventArgs e) => InvalidateChildren();
private void OwnerPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == Visual.IsVisibleProperty)
{
var parent = Owner.GetVisualParent();
if (parent is Control c)
(GetOrCreatePeer(c) as ControlAutomationPeer)?.InvalidateChildren();
}
else if (e.Property == Visual.TransformedBoundsProperty)
{
RaisePropertyChangedEvent(
AutomationElementIdentifiers.BoundingRectangleProperty,
GetBounds((TransformedBounds?)e.OldValue),
GetBounds((TransformedBounds?)e.NewValue));
}
else if (e.Property == Visual.VisualParentProperty)
{
InvalidateParent();
}
}
private void EnsureConnected()
{
if (!_parentValid)
{
var parent = Owner.GetVisualParent();
while (parent is object)
{
if (parent is Control c)
{
var parentPeer = GetOrCreatePeer(c);
parentPeer.GetChildren();
}
parent = parent.GetVisualParent();
}
_parentValid = true;
}
}
}
}