Browse Source

Initial implementation of UI automation.

Follows WPF/UWP API as closely as possible. Limited to win32 right now. Broken in many places.
ui-automation-test
Steven Kirk 6 years ago
parent
commit
2a44d8b564
  1. 28
      src/Avalonia.Controls/Automation/AutomationElementIdentifiers.cs
  2. 28
      src/Avalonia.Controls/Automation/AutomationLiveSetting.cs
  3. 539
      src/Avalonia.Controls/Automation/AutomationProperties.cs
  4. 11
      src/Avalonia.Controls/Automation/AutomationProperty.cs
  5. 10
      src/Avalonia.Controls/Automation/ElementNotEnabledException.cs
  6. 15
      src/Avalonia.Controls/Automation/ExpandCollapsePatternIdentifiers.cs
  7. 29
      src/Avalonia.Controls/Automation/ExpandCollapseState.cs
  8. 26
      src/Avalonia.Controls/Automation/IsOffscreenBehavior.cs
  9. 227
      src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs
  10. 29
      src/Avalonia.Controls/Automation/Peers/ButtonAutomationPeer.cs
  11. 20
      src/Avalonia.Controls/Automation/Peers/CheckBoxAutomationPeer.cs
  12. 123
      src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs
  13. 39
      src/Avalonia.Controls/Automation/Peers/ContentControlAutomationPeer.cs
  14. 196
      src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs
  15. 20
      src/Avalonia.Controls/Automation/Peers/ImageAutomationPeer.cs
  16. 57
      src/Avalonia.Controls/Automation/Peers/ItemsControlAutomationPeer.cs
  17. 82
      src/Avalonia.Controls/Automation/Peers/ListItemAutomationPeer.cs
  18. 22
      src/Avalonia.Controls/Automation/Peers/MenuAutomationPeer.cs
  19. 39
      src/Avalonia.Controls/Automation/Peers/MenuItemAutomationPeer.cs
  20. 28
      src/Avalonia.Controls/Automation/Peers/NoneAutomationPeer.cs
  21. 36
      src/Avalonia.Controls/Automation/Peers/PopupRootAutomationPeer.cs
  22. 34
      src/Avalonia.Controls/Automation/Peers/RangeBaseAutomationPeer.cs
  23. 175
      src/Avalonia.Controls/Automation/Peers/ScrollViewerAutomationPeer.cs
  24. 87
      src/Avalonia.Controls/Automation/Peers/SelectingItemsControlAutomationPeer.cs
  25. 20
      src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs
  26. 20
      src/Avalonia.Controls/Automation/Peers/TabControlAutomationPeer.cs
  27. 18
      src/Avalonia.Controls/Automation/Peers/TabItemAutomationPeer.cs
  28. 28
      src/Avalonia.Controls/Automation/Peers/TextAutomationPeer.cs
  29. 21
      src/Avalonia.Controls/Automation/Peers/TextBoxAutomationPeer.cs
  30. 39
      src/Avalonia.Controls/Automation/Peers/ToggleButtonAutomationPeer.cs
  31. 32
      src/Avalonia.Controls/Automation/Peers/UnrealizedElementAutomationPeer.cs
  32. 39
      src/Avalonia.Controls/Automation/Peers/WindowAutomationPeer.cs
  33. 72
      src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs
  34. 32
      src/Avalonia.Controls/Automation/Platform/IAutomationNode.cs
  35. 18
      src/Avalonia.Controls/Automation/Platform/IAutomationNodeFactory.cs
  36. 20
      src/Avalonia.Controls/Automation/Platform/IRootAutomationNode.cs
  37. 24
      src/Avalonia.Controls/Automation/Provider/IExpandCollapseProvider.cs
  38. 15
      src/Avalonia.Controls/Automation/Provider/IInvokeProvider.cs
  39. 37
      src/Avalonia.Controls/Automation/Provider/IRangeValueProvider.cs
  40. 14
      src/Avalonia.Controls/Automation/Provider/IRootProvider.cs
  41. 71
      src/Avalonia.Controls/Automation/Provider/IScrollProvider.cs
  42. 37
      src/Avalonia.Controls/Automation/Provider/ISelectionItemProvider .cs
  43. 29
      src/Avalonia.Controls/Automation/Provider/ISelectionProvider.cs
  44. 40
      src/Avalonia.Controls/Automation/Provider/IToggleProvider.cs
  45. 31
      src/Avalonia.Controls/Automation/Provider/IValueProvider.cs
  46. 30
      src/Avalonia.Controls/Automation/RangeValuePatternIdentifiers.cs
  47. 45
      src/Avalonia.Controls/Automation/ScrollPatternIdentifiers.cs
  48. 25
      src/Avalonia.Controls/Automation/SelectionPatternIdentifiers.cs
  49. 9
      src/Avalonia.Controls/Button.cs
  50. 6
      src/Avalonia.Controls/CheckBox.cs
  51. 7
      src/Avalonia.Controls/ComboBox.cs
  52. 21
      src/Avalonia.Controls/Control.cs
  53. 7
      src/Avalonia.Controls/Image.cs
  54. 7
      src/Avalonia.Controls/ItemsControl.cs
  55. 8
      src/Avalonia.Controls/ListBoxItem.cs
  56. 7
      src/Avalonia.Controls/Menu.cs
  57. 7
      src/Avalonia.Controls/MenuItem.cs
  58. 14
      src/Avalonia.Controls/Primitives/Popup.cs
  59. 7
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  60. 7
      src/Avalonia.Controls/Primitives/ToggleButton.cs
  61. 7
      src/Avalonia.Controls/ScrollViewer.cs
  62. 7
      src/Avalonia.Controls/Slider.cs
  63. 7
      src/Avalonia.Controls/TabControl.cs
  64. 7
      src/Avalonia.Controls/TabItem.cs
  65. 9
      src/Avalonia.Controls/TextBlock.cs
  66. 7
      src/Avalonia.Controls/TextBox.cs
  67. 7
      src/Avalonia.Controls/Window.cs
  68. 32
      src/Avalonia.Input/KeyboardDevice.cs
  69. 19
      src/Windows/Avalonia.Win32/Automation/AutomationNode.ExpandCollapse.cs
  70. 19
      src/Windows/Avalonia.Win32/Automation/AutomationNode.RangeValue.cs
  71. 32
      src/Windows/Avalonia.Win32/Automation/AutomationNode.Scroll.cs
  72. 38
      src/Windows/Avalonia.Win32/Automation/AutomationNode.Selection.cs
  73. 13
      src/Windows/Avalonia.Win32/Automation/AutomationNode.Toggle.cs
  74. 18
      src/Windows/Avalonia.Win32/Automation/AutomationNode.Value.cs
  75. 328
      src/Windows/Avalonia.Win32/Automation/AutomationNode.cs
  76. 20
      src/Windows/Avalonia.Win32/Automation/AutomationNodeFactory.cs
  77. 72
      src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs
  78. 3
      src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
  79. 26
      src/Windows/Avalonia.Win32/Interop/Automation/IDockProvider.cs
  80. 16
      src/Windows/Avalonia.Win32/Interop/Automation/IExpandCollapseProvider.cs
  81. 17
      src/Windows/Avalonia.Win32/Interop/Automation/IGridItemProvider.cs
  82. 15
      src/Windows/Avalonia.Win32/Interop/Automation/IGridProvider.cs
  83. 19
      src/Windows/Avalonia.Win32/Interop/Automation/IInvokeProvider.cs
  84. 16
      src/Windows/Avalonia.Win32/Interop/Automation/IMultipleViewProvider.cs
  85. 19
      src/Windows/Avalonia.Win32/Interop/Automation/IRangeValueProvider.cs
  86. 15
      src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderAdviseEvents.cs
  87. 34
      src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderFragment.cs
  88. 14
      src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderFragmentRoot.cs
  89. 285
      src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderSimple.cs
  90. 14
      src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderSimple2.cs
  91. 13
      src/Windows/Avalonia.Win32/Interop/Automation/IScrollItemProvider.cs
  92. 21
      src/Windows/Avalonia.Win32/Interop/Automation/IScrollProvider.cs
  93. 17
      src/Windows/Avalonia.Win32/Interop/Automation/ISelectionItemProvider.cs
  94. 15
      src/Windows/Avalonia.Win32/Interop/Automation/ISelectionProvider.cs
  95. 26
      src/Windows/Avalonia.Win32/Interop/Automation/ISynchronizedInputProvider.cs
  96. 14
      src/Windows/Avalonia.Win32/Interop/Automation/ITableItemProvider.cs
  97. 24
      src/Windows/Avalonia.Win32/Interop/Automation/ITableProvider.cs
  98. 30
      src/Windows/Avalonia.Win32/Interop/Automation/ITextProvider.cs
  99. 48
      src/Windows/Avalonia.Win32/Interop/Automation/ITextRangeProvider.cs
  100. 15
      src/Windows/Avalonia.Win32/Interop/Automation/IToggleProvider.cs

28
src/Avalonia.Controls/Automation/AutomationElementIdentifiers.cs

@ -0,0 +1,28 @@
using Avalonia.Automation.Peers;
namespace Avalonia.Automation
{
/// <summary>
/// Contains values used as automation property identifiers by UI Automation providers.
/// </summary>
public static class AutomationElementIdentifiers
{
/// <summary>
/// Identifies the bounding rectangle automation property. The bounding rectangle property
/// value is returned by the <see cref="AutomationPeer.GetBoundingRectangle"/> method.
/// </summary>
public static AutomationProperty BoundingRectangleProperty { get; } = new();
/// <summary>
/// Identifies the class name automation property. The class name property value is returned
/// by the <see cref="AutomationPeer.GetClassName"/> method.
/// </summary>
public static AutomationProperty ClassNameProperty { get; } = new();
/// <summary>
/// Identifies the name automation property. The class name property value is returned
/// by the <see cref="AutomationPeer.GetName"/> method.
/// </summary>
public static AutomationProperty NameProperty { get; } = new();
}
}

28
src/Avalonia.Controls/Automation/AutomationLiveSetting.cs

@ -0,0 +1,28 @@
namespace Avalonia.Automation
{
/// <summary>
/// Describes the notification characteristics of a particular live region
/// </summary>
public enum AutomationLiveSetting
{
/// <summary>
/// The element does not send notifications if the content of the live region has changed.
/// </summary>
Off = 0,
/// <summary>
/// The element sends non-interruptive notifications if the content of the live region has
/// changed. With this setting, UI Automation clients and assistive technologies are expected
/// to not interrupt the user to inform of changes to the live region.
/// </summary>
Polite = 1,
/// <summary>
/// The element sends interruptive notifications if the content of the live region has changed.
/// With this setting, UI Automation clients and assistive technologies are expected to interrupt
/// the user to inform of changes to the live region.
/// </summary>
Assertive = 2,
}
}

539
src/Avalonia.Controls/Automation/AutomationProperties.cs

@ -0,0 +1,539 @@
using System;
using Avalonia.Controls;
namespace Avalonia.Automation
{
public static class AutomationProperties
{
internal const int AutomationPositionInSetDefault = -1;
internal const int AutomationSizeOfSetDefault = -1;
/// <summary>
/// Defines the AutomationProperties.AcceleratorKey attached property.
/// </summary>
public static readonly AttachedProperty<string> AcceleratorKeyProperty =
AvaloniaProperty.RegisterAttached<StyledElement, string>(
"AcceleratorKey",
typeof(AutomationProperties));
/// <summary>
/// Defines the AutomationProperties.AccessKey attached property
/// </summary>
public static readonly AttachedProperty<string> AccessKeyProperty =
AvaloniaProperty.RegisterAttached<StyledElement, string>(
"AccessKey",
typeof(AutomationProperties));
/// <summary>
/// Defines the AutomationProperties.AutomationId attached property.
/// </summary>
public static readonly AttachedProperty<string> AutomationIdProperty =
AvaloniaProperty.RegisterAttached<StyledElement, string>(
"AutomationId",
typeof(AutomationProperties));
/// <summary>
/// Defines the AutomationProperties.HelpText attached property.
/// </summary>
public static readonly AttachedProperty<string> HelpTextProperty =
AvaloniaProperty.RegisterAttached<StyledElement, string>(
"HelpText",
typeof(AutomationProperties));
/// <summary>
/// Defines the AutomationProperties.IsColumnHeader attached property.
/// </summary>
public static readonly AttachedProperty<bool> IsColumnHeaderProperty =
AvaloniaProperty.RegisterAttached<StyledElement, bool>(
"IsColumnHeader",
typeof(AutomationProperties),
false);
/// <summary>
/// Defines the AutomationProperties.IsRequiredForForm attached property.
/// </summary>
public static readonly AttachedProperty<bool> IsRequiredForFormProperty =
AvaloniaProperty.RegisterAttached<StyledElement, bool>(
"IsRequiredForForm",
typeof(AutomationProperties),
false);
/// <summary>
/// Defines the AutomationProperties.IsRowHeader attached property.
/// </summary>
public static readonly AttachedProperty<bool> IsRowHeaderProperty =
AvaloniaProperty.RegisterAttached<StyledElement, bool>(
"IsRowHeader",
typeof(AutomationProperties),
false);
/// <summary>
/// Defines the AutomationProperties.IsOffscreenBehavior attached property.
/// </summary>
public static readonly AttachedProperty<IsOffscreenBehavior> IsOffscreenBehaviorProperty =
AvaloniaProperty.RegisterAttached<StyledElement, IsOffscreenBehavior>(
"IsOffscreenBehavior",
typeof(AutomationProperties),
IsOffscreenBehavior.Default);
/// <summary>
/// Defines the AutomationProperties.ItemStatus attached property.
/// </summary>
public static readonly AttachedProperty<string> ItemStatusProperty =
AvaloniaProperty.RegisterAttached<StyledElement, string>(
"ItemStatus",
typeof(AutomationProperties));
/// <summary>
/// Defines the AutomationProperties.ItemType attached property.
/// </summary>
public static readonly AttachedProperty<string> ItemTypeProperty =
AvaloniaProperty.RegisterAttached<StyledElement, string>(
"ItemType",
typeof(AutomationProperties));
/// <summary>
/// Defines the AutomationProperties.LabeledBy attached property.
/// </summary>
public static readonly AttachedProperty<IControl> LabeledByProperty =
AvaloniaProperty.RegisterAttached<StyledElement, IControl>(
"LabeledBy",
typeof(AutomationProperties));
/// <summary>
/// Defines the AutomationProperties.LiveSetting attached property.
/// </summary>
public static readonly AttachedProperty<AutomationLiveSetting> LiveSettingProperty =
AvaloniaProperty.RegisterAttached<StyledElement, AutomationLiveSetting>(
"LiveSetting",
typeof(AutomationProperties),
AutomationLiveSetting.Off);
/// <summary>
/// Defines the AutomationProperties.Name attached attached property.
/// </summary>
public static readonly AttachedProperty<string> NameProperty =
AvaloniaProperty.RegisterAttached<StyledElement, string>(
"Name",
typeof(AutomationProperties));
/// <summary>
/// Defines the AutomationProperties.PositionInSet attached property.
/// </summary>
/// <remarks>
/// The PositionInSet property describes the ordinal location of the element within a set
/// of elements which are considered to be siblings. PositionInSet works in coordination
/// with the SizeOfSet property to describe the ordinal location in the set.
/// </remarks>
public static readonly AttachedProperty<int> PositionInSetProperty =
AvaloniaProperty.RegisterAttached<StyledElement, int>(
"PositionInSet",
typeof(AutomationProperties),
AutomationPositionInSetDefault);
/// <summary>
/// Defines the AutomationProperties.SizeOfSet attached property.
/// </summary>
/// <remarks>
/// The SizeOfSet property describes the count of automation elements in a group or set
/// that are considered to be siblings. SizeOfSet works in coordination with the PositionInSet
/// property to describe the count of items in the set.
/// </remarks>
public static readonly AttachedProperty<int> SizeOfSetProperty =
AvaloniaProperty.RegisterAttached<StyledElement, int>(
"SizeOfSet",
typeof(AutomationProperties),
AutomationSizeOfSetDefault);
/// <summary>
/// Helper for setting AcceleratorKey property on a StyledElement.
/// </summary>
public static void SetAcceleratorKey(StyledElement element, string value)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(AcceleratorKeyProperty, value);
}
/// <summary>
/// Helper for reading AcceleratorKey property from a StyledElement.
/// </summary>
public static string GetAcceleratorKey(StyledElement element)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
return ((string)element.GetValue(AcceleratorKeyProperty));
}
/// <summary>
/// Helper for setting AccessKey property on a StyledElement.
/// </summary>
public static void SetAccessKey(StyledElement element, string value)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(AccessKeyProperty, value);
}
/// <summary>
/// Helper for reading AccessKey property from a StyledElement.
/// </summary>
public static string GetAccessKey(StyledElement element)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
return ((string)element.GetValue(AccessKeyProperty));
}
/// <summary>
/// Helper for setting AutomationId property on a StyledElement.
/// </summary>
public static void SetAutomationId(StyledElement element, string value)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(AutomationIdProperty, value);
}
/// <summary>
/// Helper for reading AutomationId property from a StyledElement.
/// </summary>
public static string GetAutomationId(StyledElement element)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
return element.GetValue(AutomationIdProperty);
}
/// <summary>
/// Helper for setting HelpText property on a StyledElement.
/// </summary>
public static void SetHelpText(StyledElement element, string value)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(HelpTextProperty, value);
}
/// <summary>
/// Helper for reading HelpText property from a StyledElement.
/// </summary>
public static string GetHelpText(StyledElement element)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
return ((string)element.GetValue(HelpTextProperty));
}
/// <summary>
/// Helper for setting IsColumnHeader property on a StyledElement.
/// </summary>
public static void SetIsColumnHeader(StyledElement element, bool value)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(IsColumnHeaderProperty, value);
}
/// <summary>
/// Helper for reading IsColumnHeader property from a StyledElement.
/// </summary>
public static bool GetIsColumnHeader(StyledElement element)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
return ((bool)element.GetValue(IsColumnHeaderProperty));
}
/// <summary>
/// Helper for setting IsRequiredForForm property on a StyledElement.
/// </summary>
public static void SetIsRequiredForForm(StyledElement element, bool value)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(IsRequiredForFormProperty, value);
}
/// <summary>
/// Helper for reading IsRequiredForForm property from a StyledElement.
/// </summary>
public static bool GetIsRequiredForForm(StyledElement element)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
return ((bool)element.GetValue(IsRequiredForFormProperty));
}
/// <summary>
/// Helper for reading IsRowHeader property from a StyledElement.
/// </summary>
public static bool GetIsRowHeader(StyledElement element)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
return ((bool)element.GetValue(IsRowHeaderProperty));
}
/// <summary>
/// Helper for setting IsRowHeader property on a StyledElement.
/// </summary>
public static void SetIsRowHeader(StyledElement element, bool value)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(IsRowHeaderProperty, value);
}
/// <summary>
/// Helper for setting IsOffscreenBehavior property on a StyledElement.
/// </summary>
public static void SetIsOffscreenBehavior(StyledElement element, IsOffscreenBehavior value)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(IsOffscreenBehaviorProperty, value);
}
/// <summary>
/// Helper for reading IsOffscreenBehavior property from a StyledElement.
/// </summary>
public static IsOffscreenBehavior GetIsOffscreenBehavior(StyledElement element)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
return ((IsOffscreenBehavior)element.GetValue(IsOffscreenBehaviorProperty));
}
/// <summary>
/// Helper for setting ItemStatus property on a StyledElement.
/// </summary>
public static void SetItemStatus(StyledElement element, string value)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(ItemStatusProperty, value);
}
/// <summary>
/// Helper for reading ItemStatus property from a StyledElement.
/// </summary>
public static string GetItemStatus(StyledElement element)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
return ((string)element.GetValue(ItemStatusProperty));
}
/// <summary>
/// Helper for setting ItemType property on a StyledElement.
/// </summary>
public static void SetItemType(StyledElement element, string value)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(ItemTypeProperty, value);
}
/// <summary>
/// Helper for reading ItemType property from a StyledElement.
/// </summary>
public static string GetItemType(StyledElement element)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
return ((string)element.GetValue(ItemTypeProperty));
}
/// <summary>
/// Helper for setting LabeledBy property on a StyledElement.
/// </summary>
public static void SetLabeledBy(StyledElement element, IControl value)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(LabeledByProperty, value);
}
/// <summary>
/// Helper for reading LabeledBy property from a StyledElement.
/// </summary>
public static IControl GetLabeledBy(StyledElement element)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
return element.GetValue(LabeledByProperty);
}
/// <summary>
/// Helper for setting LiveSetting property on a StyledElement.
/// </summary>
public static void SetLiveSetting(StyledElement element, AutomationLiveSetting value)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(LiveSettingProperty, value);
}
/// <summary>
/// Helper for reading LiveSetting property from a StyledElement.
/// </summary>
public static AutomationLiveSetting GetLiveSetting(StyledElement element)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
return ((AutomationLiveSetting)element.GetValue(LiveSettingProperty));
}
/// <summary>
/// Helper for setting Name property on a StyledElement.
/// </summary>
public static void SetName(StyledElement element, string value)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(NameProperty, value);
}
/// <summary>
/// Helper for reading Name property from a StyledElement.
/// </summary>
public static string GetName(StyledElement element)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
return ((string)element.GetValue(NameProperty));
}
/// <summary>
/// Helper for setting PositionInSet property on a StyledElement.
/// </summary>
public static void SetPositionInSet(StyledElement element, int value)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(PositionInSetProperty, value);
}
/// <summary>
/// Helper for reading PositionInSet property from a StyledElement.
/// </summary>
public static int GetPositionInSet(StyledElement element)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
return ((int)element.GetValue(PositionInSetProperty));
}
/// <summary>
/// Helper for setting SizeOfSet property on a StyledElement.
/// </summary>
public static void SetSizeOfSet(StyledElement element, int value)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
element.SetValue(SizeOfSetProperty, value);
}
/// <summary>
/// Helper for reading SizeOfSet property from a StyledElement.
/// </summary>
public static int GetSizeOfSet(StyledElement element)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}
return ((int)element.GetValue(SizeOfSetProperty));
}
}
}

11
src/Avalonia.Controls/Automation/AutomationProperty.cs

@ -0,0 +1,11 @@
namespace Avalonia.Automation
{
/// <summary>
/// Identifies a property of <see cref="AutomationElementIdentifiers"/> or of a specific
/// control pattern.
/// </summary>
public sealed class AutomationProperty
{
internal AutomationProperty() { }
}
}

10
src/Avalonia.Controls/Automation/ElementNotEnabledException.cs

@ -0,0 +1,10 @@
using System;
namespace Avalonia.Automation
{
public class ElementNotEnabledException : Exception
{
public ElementNotEnabledException() : base("Element not enabled.") { }
public ElementNotEnabledException(string message) : base(message) { }
}
}

15
src/Avalonia.Controls/Automation/ExpandCollapsePatternIdentifiers.cs

@ -0,0 +1,15 @@
using Avalonia.Automation.Provider;
namespace Avalonia.Automation
{
/// <summary>
/// Contains values used as identifiers by <see cref="IExpandCollapseProvider"/>.
/// </summary>
public static class ExpandCollapsePatternIdentifiers
{
/// <summary>
/// Identifies <see cref="IExpandCollapseProvider.ExpandCollapseState"/> automation property.
/// </summary>
public static AutomationProperty ExpandCollapseStateProperty { get; } = new();
}
}

29
src/Avalonia.Controls/Automation/ExpandCollapseState.cs

@ -0,0 +1,29 @@
namespace Avalonia.Automation
{
/// <summary>
/// Contains values that specify the <see cref="ExpandCollapseState"/> of a UI Automation element.
/// </summary>
public enum ExpandCollapseState
{
/// <summary>
/// No child nodes, controls, or content of the UI Automation element are displayed.
/// </summary>
Collapsed,
/// <summary>
/// All child nodes, controls or content of the UI Automation element are displayed.
/// </summary>
Expanded,
/// <summary>
/// The UI Automation element has no child nodes, controls, or content to display.
/// </summary>
LeafNode,
/// <summary>
/// Some, but not all, child nodes, controls, or content of the UI Automation element are
/// displayed.
/// </summary>
PartiallyExpanded
}
}

26
src/Avalonia.Controls/Automation/IsOffscreenBehavior.cs

@ -0,0 +1,26 @@
namespace Avalonia.Automation
{
/// <summary>
/// This enum offers different ways of evaluating the IsOffscreen AutomationProperty
/// </summary>
public enum IsOffscreenBehavior
{
/// <summary>
/// The AutomationProperty IsOffscreen is calculated based on IsVisible.
/// </summary>
Default,
/// <summary>
/// The AutomationProperty IsOffscreen is false.
/// </summary>
Onscreen,
/// <summary>
/// The AutomationProperty IsOffscreen if true.
/// </summary>
Offscreen,
/// <summary>
/// The AutomationProperty IsOffscreen is calculated based on clip regions.
/// </summary>
FromClip,
}
}

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

@ -0,0 +1,227 @@
using System.Collections.Generic;
using System.Globalization;
using Avalonia.Automation.Platform;
#nullable enable
namespace Avalonia.Automation.Peers
{
public enum AutomationControlType
{
Button,
Calendar,
CheckBox,
ComboBox,
Edit,
Hyperlink,
Image,
ListItem,
List,
Menu,
MenuBar,
MenuItem,
ProgressBar,
RadioButton,
ScrollBar,
Slider,
Spinner,
StatusBar,
Tab,
TabItem,
Text,
ToolBar,
ToolTip,
Tree,
TreeItem,
Custom,
Group,
Thumb,
DataGrid,
DataItem,
Document,
SplitButton,
Window,
Pane,
Header,
HeaderItem,
Table,
TitleBar,
Separator,
}
/// <summary>
/// Provides a base class that exposes an element to UI Automation.
/// </summary>
public abstract class AutomationPeer
{
/// <summary>
/// Initializes a new instance of the <see cref="AutomationPeer"/> class.
/// </summary>
/// <param name="factory">
/// The factory to use to create the platform automation node.
/// </param>
protected AutomationPeer(IAutomationNodeFactory factory)
{
Node = factory.CreateNode(this);
}
/// <summary>
/// Gets the related node in the platform UI Automation tree.
/// </summary>
public IAutomationNode Node { get; }
/// <summary>
/// Attempts to bring the element associated with the automation peer into view.
/// </summary>
public void BringIntoView() => BringIntoViewCore();
/// <summary>
/// Gets the control type for the element that is associated with the UI Automation peer.
/// </summary>
public AutomationControlType GetAutomationControlType() => GetAutomationControlTypeCore();
/// <summary>
/// Gets the automation ID of the element that is associated with the UI Automation peer.
/// </summary>
public string? GetAutomationId() => GetAutomationIdCore();
/// <summary>
/// Gets the bounding rectangle of the element that is associated with the automation peer
/// in top-level coordinates.
/// </summary>
public Rect GetBoundingRectangle() => GetBoundingRectangleCore();
/// <summary>
/// Gets the child automation peers.
/// </summary>
public IReadOnlyList<AutomationPeer> GetChildren() => GetOrCreateChildrenCore();
/// <summary>
/// Gets a string that describes the class of the element.
/// </summary>
public string GetClassName() => GetClassNameCore() ?? string.Empty;
/// <summary>
/// Gets a human-readable localized string that represents the type of the control that is
/// associated with this automation peer.
/// </summary>
public string GetLocalizedControlType() => GetLocalizedControlTypeCore();
/// <summary>
/// Gets text that describes the element that is associated with this automation peer.
/// </summary>
public string GetName() => GetNameCore() ?? string.Empty;
/// <summary>
/// Gets the <see cref="AutomationPeer"/> that is the parent of this <see cref="AutomationPeer"/>.
/// </summary>
/// <returns></returns>
public AutomationPeer? GetParent() => GetParentCore();
/// <summary>
/// Gets a value that indicates whether the element that is associated with this automation
/// peer currently has keyboard focus.
/// </summary>
public bool HasKeyboardFocus() => HasKeyboardFocusCore();
/// <summary>
/// Gets a value that indicates whether the element that is associated with this automation
/// peer contains data that is presented to the user.
/// </summary>
public bool IsContentElement() => IsControlElement() && IsContentElementCore();
/// <summary>
/// Gets a value that indicates whether the element is understood by the user as
/// interactive or as contributing to the logical structure of the control in the GUI.
/// </summary>
public bool IsControlElement() => IsControlElementCore();
/// <summary>
/// Gets a value indicating whether the control is enabled for user interaction.
/// </summary>
public bool IsEnabled() => IsEnabledCore();
/// <summary>
/// Gets a value that indicates whether the element can accept keyboard focus.
/// </summary>
/// <returns></returns>
public bool IsKeyboardFocusable() => IsKeyboardFocusableCore();
/// <summary>
/// Sets the keyboard focus on the element that is associated with this automation peer.
/// </summary>
public void SetFocus() => SetFocusCore();
/// <summary>
/// Shows the context menu for the element that is associated with this automation peer.
/// </summary>
/// <returns>true if a context menu is present for the element; otherwise false.</returns>
public bool ShowContextMenu() => ShowContextMenuCore();
/// <summary>
/// Raises an event to notify the automation client of a changed property value.
/// </summary>
/// <param name="automationProperty">The property that changed.</param>
/// <param name="oldValue">The previous value of the property.</param>
/// <param name="newValue">The new value of the property.</param>
public void RaisePropertyChangedEvent(
AutomationProperty automationProperty,
object? oldValue,
object? newValue)
{
Node.PropertyChanged(automationProperty, oldValue, newValue);
}
protected virtual string GetLocalizedControlTypeCore()
{
var controlType = GetAutomationControlType();
return controlType switch
{
AutomationControlType.CheckBox => "check box",
AutomationControlType.ComboBox => "combo box",
AutomationControlType.ListItem => "list item",
AutomationControlType.MenuBar => "menu bar",
AutomationControlType.MenuItem => "menu item",
AutomationControlType.ProgressBar => "progress bar",
AutomationControlType.RadioButton => "radio button",
AutomationControlType.ScrollBar => "scroll bar",
AutomationControlType.StatusBar => "status bar",
AutomationControlType.TabItem => "tab item",
AutomationControlType.ToolBar => "toolbar",
AutomationControlType.ToolTip => "tooltip",
AutomationControlType.TreeItem => "tree item",
AutomationControlType.Custom => "custom",
AutomationControlType.DataGrid => "data grid",
AutomationControlType.DataItem => "data item",
AutomationControlType.SplitButton => "split button",
AutomationControlType.HeaderItem => "header item",
AutomationControlType.TitleBar => "title bar",
_ => controlType.ToString().ToLowerInvariant(),
};
}
protected abstract void BringIntoViewCore();
protected abstract AutomationControlType GetAutomationControlTypeCore();
protected abstract string? GetAutomationIdCore();
protected abstract Rect GetBoundingRectangleCore();
protected abstract IReadOnlyList<AutomationPeer> GetOrCreateChildrenCore();
protected abstract string GetClassNameCore();
protected abstract string? GetNameCore();
protected abstract AutomationPeer? GetParentCore();
protected abstract bool HasKeyboardFocusCore();
protected abstract bool IsContentElementCore();
protected abstract bool IsControlElementCore();
protected abstract bool IsEnabledCore();
protected abstract bool IsKeyboardFocusableCore();
protected abstract void SetFocusCore();
protected abstract bool ShowContextMenuCore();
protected internal abstract bool TrySetParent(AutomationPeer? parent);
protected void EnsureEnabled()
{
if (!IsEnabled())
throw new ElementNotEnabledException();
}
}
}

29
src/Avalonia.Controls/Automation/Peers/ButtonAutomationPeer.cs

@ -0,0 +1,29 @@
using Avalonia.Automation.Platform;
using Avalonia.Automation.Provider;
using Avalonia.Controls;
#nullable enable
namespace Avalonia.Automation.Peers
{
public class ButtonAutomationPeer : ContentControlAutomationPeer,
IInvokeProvider
{
public ButtonAutomationPeer(IAutomationNodeFactory factory, Button owner)
: base(factory, owner)
{
}
public void Invoke()
{
EnsureEnabled();
(Owner as Button)?.PerformClick();
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Button;
}
}
}

20
src/Avalonia.Controls/Automation/Peers/CheckBoxAutomationPeer.cs

@ -0,0 +1,20 @@
using Avalonia.Automation.Platform;
using Avalonia.Controls;
#nullable enable
namespace Avalonia.Automation.Peers
{
public class CheckBoxAutomationPeer : ToggleButtonAutomationPeer
{
public CheckBoxAutomationPeer(IAutomationNodeFactory factory, CheckBox owner)
: base(factory, owner)
{
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.CheckBox;
}
}
}

123
src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs

@ -0,0 +1,123 @@
using System.Collections.Generic;
using Avalonia.Automation.Platform;
using Avalonia.Automation.Provider;
using Avalonia.Controls;
#nullable enable
namespace Avalonia.Automation.Peers
{
public class ComboBoxAutomationPeer : SelectingItemsControlAutomationPeer,
IExpandCollapseProvider
{
private UnrealizedSelectionPeer[]? _selection;
public ComboBoxAutomationPeer(IAutomationNodeFactory factory, ComboBox owner)
: base(factory, owner)
{
}
public new ComboBox Owner => (ComboBox)base.Owner;
public ExpandCollapseState ExpandCollapseState => ToState(Owner.IsDropDownOpen);
public void Collapse() => Owner.IsDropDownOpen = false;
public void Expand() => Owner.IsDropDownOpen = true;
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.ComboBox;
}
protected override IReadOnlyList<AutomationPeer>? GetSelectionCore()
{
if (ExpandCollapseState == ExpandCollapseState.Expanded)
return base.GetSelectionCore();
// If the combo box is not open then we won't have an ItemsPresenter so the default
// GetSelectionCore implementation won't work. For this case we create a separate
// peer to represent the unrealized item.
if (Owner.SelectedItem is object selection)
{
_selection ??= new[] { new UnrealizedSelectionPeer(Node.Factory, this) };
_selection[0].Item = selection;
return _selection;
}
return null;
}
protected override void OwnerPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
{
base.OwnerPropertyChanged(sender, e);
if (e.Property == ComboBox.IsDropDownOpenProperty)
{
RaisePropertyChangedEvent(
ExpandCollapsePatternIdentifiers.ExpandCollapseStateProperty,
ToState((bool)e.OldValue!),
ToState((bool)e.NewValue!));
}
}
private ExpandCollapseState ToState(bool value)
{
return value ? ExpandCollapseState.Expanded : ExpandCollapseState.Collapsed;
}
private class UnrealizedSelectionPeer : UnrealizedElementAutomationPeer
{
private readonly ComboBoxAutomationPeer _owner;
private object? _item;
public UnrealizedSelectionPeer(IAutomationNodeFactory factory, ComboBoxAutomationPeer owner)
: base(factory)
{
_owner = owner;
}
public object? Item
{
get => _item;
set
{
if (_item != value)
{
var oldValue = GetNameCore();
_item = value;
RaisePropertyChangedEvent(
AutomationElementIdentifiers.NameProperty,
oldValue,
GetNameCore());
}
}
}
protected override string? GetAutomationIdCore() => null;
protected override string GetClassNameCore() => typeof(ComboBoxItem).Name;
protected override AutomationPeer? GetParentCore() => _owner;
protected override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.ListItem;
protected override string? GetNameCore()
{
if (_item is Control c)
{
var result = AutomationProperties.GetName(c);
if (result is null && c is ContentControl cc && cc.Presenter?.Child is TextBlock text)
{
result = text.Text;
}
if (result is null)
{
result = c.GetValue(ContentControl.ContentProperty)?.ToString();
}
return result;
}
return _item?.ToString();
}
}
}
}

39
src/Avalonia.Controls/Automation/Peers/ContentControlAutomationPeer.cs

@ -0,0 +1,39 @@
using Avalonia.Automation.Platform;
using Avalonia.Controls;
#nullable enable
namespace Avalonia.Automation.Peers
{
public class ContentControlAutomationPeer : ControlAutomationPeer
{
protected ContentControlAutomationPeer(IAutomationNodeFactory factory, ContentControl owner)
: base(factory, owner)
{
}
public new ContentControl Owner => (ContentControl)base.Owner;
protected override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.Pane;
protected override string? GetNameCore()
{
var result = base.GetNameCore();
if (result is null && Owner.Presenter?.Child is TextBlock text)
{
result = text.Text;
}
if (result is null)
{
result = Owner.Content?.ToString();
}
return result;
}
protected override bool IsContentElementCore() => false;
protected override bool IsControlElementCore() => false;
}
}

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

@ -0,0 +1,196 @@
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 string? GetAutomationIdCore() => AutomationProperties.GetAutomationId(Owner);
protected override Rect GetBoundingRectangleCore() => GetBounds(Owner.TransformedBounds);
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? 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 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 override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.Custom;
protected override string GetClassNameCore() => Owner.GetType().Name;
protected override string? GetNameCore() => AutomationProperties.GetName(Owner);
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();
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;
}
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;
}
}
}
}

20
src/Avalonia.Controls/Automation/Peers/ImageAutomationPeer.cs

@ -0,0 +1,20 @@
using Avalonia.Automation.Platform;
using Avalonia.Controls;
#nullable enable
namespace Avalonia.Automation.Peers
{
public class ImageAutomationPeer : ControlAutomationPeer
{
public ImageAutomationPeer(IAutomationNodeFactory factory, Control owner)
: base(factory, owner)
{
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Image;
}
}
}

57
src/Avalonia.Controls/Automation/Peers/ItemsControlAutomationPeer.cs

@ -0,0 +1,57 @@
using Avalonia.Automation.Platform;
using Avalonia.Automation.Provider;
using Avalonia.Controls;
#nullable enable
namespace Avalonia.Automation.Peers
{
public class ItemsControlAutomationPeer : ControlAutomationPeer, IScrollProvider
{
private bool _searchedForScrollable;
private IScrollProvider? _scroller;
public ItemsControlAutomationPeer(IAutomationNodeFactory factory, ItemsControl owner)
: base(factory, owner)
{
}
public new ItemsControl Owner => (ItemsControl)base.Owner;
public bool HorizontallyScrollable => _scroller?.HorizontallyScrollable ?? false;
public double HorizontalScrollPercent => _scroller?.HorizontalScrollPercent ?? -1;
public double HorizontalViewSize => _scroller?.HorizontalViewSize ?? 0;
public bool VerticallyScrollable => _scroller?.VerticallyScrollable ?? false;
public double VerticalScrollPercent => _scroller?.VerticalScrollPercent ?? -1;
public double VerticalViewSize => _scroller?.VerticalViewSize ?? 0;
protected virtual IScrollProvider? Scroller
{
get
{
if (!_searchedForScrollable)
{
if (Owner.GetValue(ListBox.ScrollProperty) is Control scrollable)
_scroller = GetOrCreatePeer(scrollable) as IScrollProvider;
_searchedForScrollable = true;
}
return _scroller;
}
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.List;
}
public void Scroll(ScrollAmount horizontalAmount, ScrollAmount verticalAmount)
{
_scroller?.Scroll(horizontalAmount, verticalAmount);
}
public void SetScrollPercent(double horizontalPercent, double verticalPercent)
{
_scroller?.SetScrollPercent(horizontalPercent, verticalPercent);
}
}
}

82
src/Avalonia.Controls/Automation/Peers/ListItemAutomationPeer.cs

@ -0,0 +1,82 @@
using System;
using Avalonia.Automation.Platform;
using Avalonia.Automation.Provider;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Selection;
#nullable enable
namespace Avalonia.Automation.Peers
{
public class ListItemAutomationPeer : ContentControlAutomationPeer,
ISelectionItemProvider
{
public ListItemAutomationPeer(IAutomationNodeFactory factory, ContentControl owner)
: base(factory, owner)
{
}
public bool IsSelected => Owner.GetValue(ListBoxItem.IsSelectedProperty);
public ISelectionProvider? SelectionContainer
{
get
{
if (Owner.Parent is Control parent)
{
var parentPeer = GetOrCreatePeer(parent);
return parentPeer as ISelectionProvider;
}
return null;
}
}
public void Select()
{
EnsureEnabled();
if (Owner.Parent is SelectingItemsControl parent)
{
var index = parent.ItemContainerGenerator.IndexFromContainer(Owner);
if (index != -1)
parent.SelectedIndex = index;
}
}
void ISelectionItemProvider.AddToSelection()
{
EnsureEnabled();
if (Owner.Parent is ItemsControl parent &&
parent.GetValue(ListBox.SelectionProperty) is ISelectionModel selectionModel)
{
var index = parent.ItemContainerGenerator.IndexFromContainer(Owner);
if (index != -1)
selectionModel.Select(index);
}
}
void ISelectionItemProvider.RemoveFromSelection()
{
EnsureEnabled();
if (Owner.Parent is ItemsControl parent &&
parent.GetValue(ListBox.SelectionProperty) is ISelectionModel selectionModel)
{
var index = parent.ItemContainerGenerator.IndexFromContainer(Owner);
if (index != -1)
selectionModel.Deselect(index);
}
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.ListItem;
}
}
}

22
src/Avalonia.Controls/Automation/Peers/MenuAutomationPeer.cs

@ -0,0 +1,22 @@
using Avalonia.Automation.Platform;
using Avalonia.Controls;
#nullable enable
namespace Avalonia.Automation.Peers
{
public class MenuAutomationPeer : ControlAutomationPeer
{
public MenuAutomationPeer(IAutomationNodeFactory factory, Menu owner)
: base(factory, owner)
{
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Menu;
}
protected override bool IsContentElementCore() => false;
}
}

39
src/Avalonia.Controls/Automation/Peers/MenuItemAutomationPeer.cs

@ -0,0 +1,39 @@
using Avalonia.Automation.Platform;
using Avalonia.Controls;
#nullable enable
namespace Avalonia.Automation.Peers
{
public class MenuItemAutomationPeer : ControlAutomationPeer
{
public MenuItemAutomationPeer(IAutomationNodeFactory factory, MenuItem owner)
: base(factory, owner)
{
}
public new MenuItem Owner => (MenuItem)base.Owner;
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.MenuItem;
}
protected override string? GetNameCore()
{
var result = base.GetNameCore();
if (result is null && Owner.HeaderPresenter.Child is TextBlock text)
{
result = text.Text;
}
if (result is null)
{
result = Owner.Header?.ToString();
}
return result;
}
}
}

28
src/Avalonia.Controls/Automation/Peers/NoneAutomationPeer.cs

@ -0,0 +1,28 @@
using Avalonia.Automation.Platform;
using Avalonia.Controls;
#nullable enable
namespace Avalonia.Automation.Peers
{
/// <summary>
/// An automation peer which represents an element that is exposed to automation as non-
/// interactive or as not contributing to the logical structure of the application.
/// </summary>
public class NoneAutomationPeer : ControlAutomationPeer
{
public NoneAutomationPeer(IAutomationNodeFactory factory, Control owner)
: base(factory, owner)
{
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Pane;
}
protected override bool IsContentElementCore() => false;
protected override bool IsControlElementCore() => false;
}
}

36
src/Avalonia.Controls/Automation/Peers/PopupRootAutomationPeer.cs

@ -0,0 +1,36 @@
using System;
using Avalonia.Automation.Platform;
using Avalonia.Controls.Primitives;
#nullable enable
namespace Avalonia.Automation.Peers
{
public class PopupRootAutomationPeer : WindowBaseAutomationPeer
{
public PopupRootAutomationPeer(IAutomationNodeFactory factory, PopupRoot owner)
: base(factory, owner)
{
if (owner.IsVisible)
StartTrackingFocus();
else
owner.Opened += OnOpened;
owner.Closed += OnClosed;
}
protected override bool IsContentElementCore() => false;
protected override bool IsControlElementCore() => false;
private void OnOpened(object sender, EventArgs e)
{
((PopupRoot)Owner).Opened -= OnOpened;
StartTrackingFocus();
}
private void OnClosed(object sender, EventArgs e)
{
((PopupRoot)Owner).Closed -= OnClosed;
StopTrackingFocus();
}
}
}

34
src/Avalonia.Controls/Automation/Peers/RangeBaseAutomationPeer.cs

@ -0,0 +1,34 @@
using Avalonia.Automation.Platform;
using Avalonia.Automation.Provider;
using Avalonia.Controls.Primitives;
#nullable enable
namespace Avalonia.Automation.Peers
{
public abstract class RangeBaseAutomationPeer : ControlAutomationPeer, IRangeValueProvider
{
public RangeBaseAutomationPeer(IAutomationNodeFactory factory, RangeBase owner)
: base(factory, owner)
{
owner.PropertyChanged += OwnerPropertyChanged;
}
public new RangeBase Owner => (RangeBase)base.Owner;
public virtual bool IsReadOnly => false;
public double Maximum => Owner.Maximum;
public double Minimum => Owner.Minimum;
public double Value => Owner.Value;
public void SetValue(double value) => Owner.Value = value;
protected virtual void OwnerPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == RangeBase.MinimumProperty)
RaisePropertyChangedEvent(RangeValuePatternIdentifiers.MinimumProperty, e.OldValue, e.NewValue);
else if (e.Property == RangeBase.MaximumProperty)
RaisePropertyChangedEvent(RangeValuePatternIdentifiers.MaximumProperty, e.OldValue, e.NewValue);
else if (e.Property == RangeBase.ValueProperty)
RaisePropertyChangedEvent(RangeValuePatternIdentifiers.ValueProperty, e.OldValue, e.NewValue);
}
}
}

175
src/Avalonia.Controls/Automation/Peers/ScrollViewerAutomationPeer.cs

@ -0,0 +1,175 @@
using System;
using Avalonia.Automation.Platform;
using Avalonia.Automation.Provider;
using Avalonia.Controls;
using Avalonia.Utilities;
#nullable enable
namespace Avalonia.Automation.Peers
{
public class ScrollViewerAutomationPeer : ControlAutomationPeer, IScrollProvider
{
public ScrollViewerAutomationPeer(IAutomationNodeFactory factory, ScrollViewer owner)
: base(factory, owner)
{
}
public new ScrollViewer Owner => (ScrollViewer)base.Owner;
public bool HorizontallyScrollable
{
get => MathUtilities.GreaterThan(Owner.Extent.Width, Owner.Viewport.Width);
}
public double HorizontalScrollPercent
{
get
{
if (!HorizontallyScrollable)
return ScrollPatternIdentifiers.NoScroll;
return (double)(Owner.Offset.X * 100.0 / (Owner.Extent.Width - Owner.Viewport.Width));
}
}
public double HorizontalViewSize
{
get
{
if (MathUtilities.IsZero(Owner.Extent.Width))
return 100;
return Math.Min(100, Owner.Viewport.Width * 100.0 / Owner.Extent.Width);
}
}
public bool VerticallyScrollable
{
get => MathUtilities.GreaterThan(Owner.Extent.Height, Owner.Viewport.Height);
}
public double VerticalScrollPercent
{
get
{
if (!VerticallyScrollable)
return ScrollPatternIdentifiers.NoScroll;
return (double)(Owner.Offset.Y * 100.0 / (Owner.Extent.Height - Owner.Viewport.Height));
}
}
public double VerticalViewSize
{
get
{
if (MathUtilities.IsZero(Owner.Extent.Height))
return 100;
return Math.Min(100, Owner.Viewport.Height * 100.0 / Owner.Extent.Height);
}
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Pane;
}
protected override bool IsContentElementCore() => false;
protected override bool IsControlElementCore()
{
// Return false if the control is part of a control template.
return Owner.TemplatedParent is null && base.IsControlElementCore();
}
public void Scroll(ScrollAmount horizontalAmount, ScrollAmount verticalAmount)
{
if (!IsEnabled())
throw new ElementNotEnabledException();
var scrollHorizontally = horizontalAmount != ScrollAmount.NoAmount;
var scrollVertically = verticalAmount != ScrollAmount.NoAmount;
if (scrollHorizontally && !HorizontallyScrollable || scrollVertically && !VerticallyScrollable)
{
throw new InvalidOperationException("Operation cannot be performed");
}
switch (horizontalAmount)
{
case ScrollAmount.LargeDecrement:
Owner.PageLeft();
break;
case ScrollAmount.SmallDecrement:
Owner.LineLeft();
break;
case ScrollAmount.SmallIncrement:
Owner.LineRight();
break;
case ScrollAmount.LargeIncrement:
Owner.PageRight();
break;
case ScrollAmount.NoAmount:
break;
default:
throw new InvalidOperationException("Operation cannot be performed");
}
switch (verticalAmount)
{
case ScrollAmount.LargeDecrement:
Owner.PageUp();
break;
case ScrollAmount.SmallDecrement:
Owner.LineUp();
break;
case ScrollAmount.SmallIncrement:
Owner.LineDown();
break;
case ScrollAmount.LargeIncrement:
Owner.PageDown();
break;
case ScrollAmount.NoAmount:
break;
default:
throw new InvalidOperationException("Operation cannot be performed");
}
}
public void SetScrollPercent(double horizontalPercent, double verticalPercent)
{
if (!IsEnabled())
throw new ElementNotEnabledException();
var scrollHorizontally = horizontalPercent != ScrollPatternIdentifiers.NoScroll;
var scrollVertically = verticalPercent != ScrollPatternIdentifiers.NoScroll;
if (scrollHorizontally && !HorizontallyScrollable || scrollVertically && !VerticallyScrollable)
{
throw new InvalidOperationException("Operation cannot be performed");
}
if (scrollHorizontally && (horizontalPercent < 0.0) || (horizontalPercent > 100.0))
{
throw new ArgumentOutOfRangeException("horizontalPercent");
}
if (scrollVertically && (verticalPercent < 0.0) || (verticalPercent > 100.0))
{
throw new ArgumentOutOfRangeException("verticalPercent");
}
var offset = Owner.Offset;
if (scrollHorizontally)
{
offset = offset.WithX((Owner.Extent.Width - Owner.Viewport.Width) * horizontalPercent * 0.01);
}
if (scrollVertically)
{
offset = offset.WithY((Owner.Extent.Height - Owner.Viewport.Height) * verticalPercent * 0.01);
}
Owner.Offset = offset;
}
}
}

87
src/Avalonia.Controls/Automation/Peers/SelectingItemsControlAutomationPeer.cs

@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using Avalonia.Automation.Platform;
using Avalonia.Automation.Provider;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Selection;
using Avalonia.VisualTree;
#nullable enable
namespace Avalonia.Automation.Peers
{
public abstract class SelectingItemsControlAutomationPeer : ItemsControlAutomationPeer,
ISelectionProvider
{
private ISelectionModel _selection;
protected SelectingItemsControlAutomationPeer(IAutomationNodeFactory factory, SelectingItemsControl owner)
: base(factory, owner)
{
_selection = owner.GetValue(ListBox.SelectionProperty);
_selection.SelectionChanged += OwnerSelectionChanged;
owner.PropertyChanged += OwnerPropertyChanged;
}
public bool CanSelectMultiple => GetSelectionModeCore().HasFlagCustom(SelectionMode.Multiple);
public bool IsSelectionRequired => GetSelectionModeCore().HasFlagCustom(SelectionMode.AlwaysSelected);
public IReadOnlyList<AutomationPeer> GetSelection() => GetSelectionCore() ?? Array.Empty<AutomationPeer>();
protected virtual IReadOnlyList<AutomationPeer>? GetSelectionCore()
{
List<AutomationPeer>? result = null;
if (Owner is SelectingItemsControl owner)
{
var selection = Owner.GetValue(ListBox.SelectionProperty);
foreach (var i in selection.SelectedIndexes)
{
var container = owner.ItemContainerGenerator.ContainerFromIndex(i);
if (container is Control c && ((IVisual)c).IsAttachedToVisualTree)
{
var peer = GetOrCreatePeer(c);
if (peer is object)
{
result ??= new List<AutomationPeer>();
result.Add(peer);
}
}
}
return result;
}
return result;
}
protected virtual SelectionMode GetSelectionModeCore()
{
return (Owner as SelectingItemsControl)?.GetValue(ListBox.SelectionModeProperty) ?? SelectionMode.Single;
}
protected virtual void OwnerPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == ListBox.SelectionProperty)
{
_selection.SelectionChanged -= OwnerSelectionChanged;
_selection = Owner.GetValue(ListBox.SelectionProperty);
_selection.SelectionChanged += OwnerSelectionChanged;
RaiseSelectionChanged();
}
}
protected virtual void OwnerSelectionChanged(object sender, SelectionModelSelectionChangedEventArgs e)
{
RaiseSelectionChanged();
}
private void RaiseSelectionChanged()
{
RaisePropertyChangedEvent(SelectionPatternIdentifiers.SelectionProperty, null, null);
}
}
}

20
src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs

@ -0,0 +1,20 @@
using Avalonia.Automation.Platform;
using Avalonia.Controls;
#nullable enable
namespace Avalonia.Automation.Peers
{
public class SliderAutomationPeer : RangeBaseAutomationPeer
{
public SliderAutomationPeer(IAutomationNodeFactory factory, Slider owner)
: base(factory, owner)
{
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Slider;
}
}
}

20
src/Avalonia.Controls/Automation/Peers/TabControlAutomationPeer.cs

@ -0,0 +1,20 @@
using Avalonia.Automation.Platform;
using Avalonia.Controls;
#nullable enable
namespace Avalonia.Automation.Peers
{
public class TabControlAutomationPeer : SelectingItemsControlAutomationPeer
{
public TabControlAutomationPeer(IAutomationNodeFactory factory, TabControl owner)
: base(factory, owner)
{
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Tab;
}
}
}

18
src/Avalonia.Controls/Automation/Peers/TabItemAutomationPeer.cs

@ -0,0 +1,18 @@
using Avalonia.Automation.Platform;
using Avalonia.Controls;
namespace Avalonia.Automation.Peers
{
public class TabItemAutomationPeer : ListItemAutomationPeer
{
public TabItemAutomationPeer(IAutomationNodeFactory factory, TabItem owner)
: base(factory, owner)
{
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.TabItem;
}
}
}

28
src/Avalonia.Controls/Automation/Peers/TextAutomationPeer.cs

@ -0,0 +1,28 @@
using Avalonia.Automation.Platform;
using Avalonia.Controls;
#nullable enable
namespace Avalonia.Automation.Peers
{
public class TextAutomationPeer : ControlAutomationPeer
{
public TextAutomationPeer(IAutomationNodeFactory factory, Control owner)
: base(factory, owner)
{
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Text;
}
protected override string? GetNameCore() => Owner.GetValue(TextBlock.TextProperty);
protected override bool IsControlElementCore()
{
// Return false if the control is part of a control template.
return Owner.TemplatedParent is null && base.IsControlElementCore();
}
}
}

21
src/Avalonia.Controls/Automation/Peers/TextBoxAutomationPeer.cs

@ -0,0 +1,21 @@
using Avalonia.Automation.Platform;
using Avalonia.Automation.Provider;
using Avalonia.Controls;
#nullable enable
namespace Avalonia.Automation.Peers
{
public class TextBoxAutomationPeer : TextAutomationPeer, IValueProvider
{
public TextBoxAutomationPeer(IAutomationNodeFactory factory, TextBox owner)
: base(factory, owner)
{
}
public new TextBox Owner => (TextBox)base.Owner;
public bool IsReadOnly => Owner.IsReadOnly;
public string? Value => Owner.Text;
public void SetValue(string? value) => Owner.Text = value;
}
}

39
src/Avalonia.Controls/Automation/Peers/ToggleButtonAutomationPeer.cs

@ -0,0 +1,39 @@
using Avalonia.Automation.Platform;
using Avalonia.Automation.Provider;
using Avalonia.Controls.Primitives;
#nullable enable
namespace Avalonia.Automation.Peers
{
public class ToggleButtonAutomationPeer : ContentControlAutomationPeer, IToggleProvider
{
public ToggleButtonAutomationPeer(IAutomationNodeFactory factory, ToggleButton owner)
: base(factory, owner)
{
}
public new ToggleButton Owner => (ToggleButton)base.Owner;
ToggleState IToggleProvider.ToggleState
{
get => Owner.IsChecked switch
{
true => ToggleState.On,
false => ToggleState.Off,
null => ToggleState.Indeterminate,
};
}
void IToggleProvider.Toggle()
{
EnsureEnabled();
Owner.PerformClick();
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Button;
}
}
}

32
src/Avalonia.Controls/Automation/Peers/UnrealizedElementAutomationPeer.cs

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using Avalonia.Automation.Platform;
#nullable enable
namespace Avalonia.Automation.Peers
{
/// <summary>
/// An automation peer which represents an unrealized element
/// </summary>
public abstract class UnrealizedElementAutomationPeer : AutomationPeer
{
protected UnrealizedElementAutomationPeer(IAutomationNodeFactory factory)
: base(factory)
{
}
public void SetParent(AutomationPeer? parent) => TrySetParent(parent);
protected override void BringIntoViewCore() => GetParent()?.BringIntoView();
protected override Rect GetBoundingRectangleCore() => GetParent()?.GetBoundingRectangle() ?? default;
protected override IReadOnlyList<AutomationPeer> GetOrCreateChildrenCore() => Array.Empty<AutomationPeer>();
protected override bool HasKeyboardFocusCore() => false;
protected override bool IsContentElementCore() => false;
protected override bool IsControlElementCore() => false;
protected override bool IsEnabledCore() => true;
protected override bool IsKeyboardFocusableCore() => false;
protected override void SetFocusCore() { }
protected override bool ShowContextMenuCore() => false;
protected internal override bool TrySetParent(AutomationPeer? parent) => false;
}
}

39
src/Avalonia.Controls/Automation/Peers/WindowAutomationPeer.cs

@ -0,0 +1,39 @@
using System;
using Avalonia.Automation.Platform;
using Avalonia.Controls;
#nullable enable
namespace Avalonia.Automation.Peers
{
public class WindowAutomationPeer : WindowBaseAutomationPeer
{
public WindowAutomationPeer(IAutomationNodeFactory factory, Window owner)
: base(factory, owner)
{
if (owner.IsVisible)
StartTrackingFocus();
else
owner.Opened += OnOpened;
owner.Closed += OnClosed;
}
public new Window Owner => (Window)base.Owner;
protected override string GetNameCore() => Owner.Title;
private void OnOpened(object sender, EventArgs e)
{
Owner.Opened -= OnOpened;
StartTrackingFocus();
}
private void OnClosed(object sender, EventArgs e)
{
Owner.Closed -= OnClosed;
StopTrackingFocus();
}
}
}

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

@ -0,0 +1,72 @@
using System.ComponentModel;
using Avalonia.Automation.Platform;
using Avalonia.Automation.Provider;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Platform;
using Avalonia.VisualTree;
#nullable enable
namespace Avalonia.Automation.Peers
{
public class WindowBaseAutomationPeer : ControlAutomationPeer, IRootProvider
{
private Control? _focus;
public WindowBaseAutomationPeer(IAutomationNodeFactory factory, WindowBase owner)
: base(factory, owner)
{
}
public new WindowBase Owner => (WindowBase)base.Owner;
public ITopLevelImpl PlatformImpl => Owner.PlatformImpl;
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Window;
}
public AutomationPeer? GetFocus() => _focus is object ? GetOrCreatePeer(_focus) : null;
public AutomationPeer? GetPeerFromPoint(Point p)
{
var hit = Owner.GetVisualAt(p)?.FindAncestorOfType<Control>(includeSelf: true);
return hit is object ? GetOrCreatePeer(hit) : null;
}
protected void StartTrackingFocus()
{
KeyboardDevice.Instance.PropertyChanged += KeyboardDevicePropertyChanged;
OnFocusChanged(KeyboardDevice.Instance.FocusedElement);
}
protected void StopTrackingFocus()
{
KeyboardDevice.Instance.PropertyChanged -= KeyboardDevicePropertyChanged;
}
private void OnFocusChanged(IInputElement? focus)
{
var oldFocus = _focus;
_focus = focus?.VisualRoot == Owner ? focus as Control : null;
if (_focus != oldFocus)
{
var peer = _focus is object ? GetOrCreatePeer(_focus) : null;
((IRootAutomationNode)Node).FocusChanged(peer);
}
}
private void KeyboardDevicePropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(KeyboardDevice.FocusedElement))
{
OnFocusChanged(KeyboardDevice.Instance.FocusedElement);
}
}
}
}

32
src/Avalonia.Controls/Automation/Platform/IAutomationNode.cs

@ -0,0 +1,32 @@
using System;
using Avalonia.Automation.Peers;
#nullable enable
namespace Avalonia.Automation.Platform
{
/// <summary>
/// Represents a platform implementation of a node in the UI Automation tree.
/// </summary>
public interface IAutomationNode
{
/// <summary>
/// Gets a factory which can be used to create child nodes.
/// </summary>
IAutomationNodeFactory Factory { get; }
/// <summary>
/// Called by the <see cref="AutomationPeer"/> when the children of the peer change.
/// </summary>
void ChildrenChanged();
/// <summary>
/// Called by the <see cref="AutomationPeer"/> when a property other than the parent,
/// children or root changes.
/// </summary>
/// <param name="property">The property that changed.</param>
/// <param name="oldValue">The previous value of the property.</param>
/// <param name="newValue">The new value of the property.</param>
void PropertyChanged(AutomationProperty property, object? oldValue, object? newValue);
}
}

18
src/Avalonia.Controls/Automation/Platform/IAutomationNodeFactory.cs

@ -0,0 +1,18 @@
using Avalonia.Automation.Peers;
#nullable enable
namespace Avalonia.Automation.Platform
{
/// <summary>
/// Creates nodes in the UI Automation tree of the underlying platform.
/// </summary>
public interface IAutomationNodeFactory
{
/// <summary>
/// Creates an automation node for a peer.
/// </summary>
/// <param name="peer">The peer.</param>
IAutomationNode CreateNode(AutomationPeer peer);
}
}

20
src/Avalonia.Controls/Automation/Platform/IRootAutomationNode.cs

@ -0,0 +1,20 @@
using Avalonia.Automation.Peers;
#nullable enable
namespace Avalonia.Automation.Platform
{
/// <summary>
/// Represents a platform implementation of a root node in the UI Automation tree.
/// </summary>
public interface IRootAutomationNode : IAutomationNode
{
/// <summary>
/// Called by the <see cref="IRootProvider"/> when its focus changes.
/// </summary>
/// <param name="focus">
/// The automation peer for the newly focused control or null if no control is focused.
/// </param>
void FocusChanged(AutomationPeer? focus);
}
}

24
src/Avalonia.Controls/Automation/Provider/IExpandCollapseProvider.cs

@ -0,0 +1,24 @@
namespace Avalonia.Automation.Provider
{
/// <summary>
/// Exposes methods and properties to support UI Automation client access to controls that
/// visually expand to display content and collapse to hide content.
/// </summary>
public interface IExpandCollapseProvider
{
/// <summary>
/// Gets the state, expanded or collapsed, of the control.
/// </summary>
ExpandCollapseState ExpandCollapseState { get; }
/// <summary>
/// Displays all child nodes, controls, or content of the control.
/// </summary>
void Expand();
/// <summary>
/// Hides all nodes, controls, or content that are descendants of the control.
/// </summary>
void Collapse();
}
}

15
src/Avalonia.Controls/Automation/Provider/IInvokeProvider.cs

@ -0,0 +1,15 @@
namespace Avalonia.Automation.Provider
{
/// <summary>
/// Exposes methods and properties to support UI Automation client access to controls that
/// initiate or perform a single, unambiguous action and do not maintain state when
/// activated.
/// </summary>
public interface IInvokeProvider
{
/// <summary>
/// Sends a request to activate a control and initiate its single, unambiguous action.
/// </summary>
void Invoke();
}
}

37
src/Avalonia.Controls/Automation/Provider/IRangeValueProvider.cs

@ -0,0 +1,37 @@
#nullable enable
namespace Avalonia.Automation.Provider
{
/// <summary>
/// Exposes methods and properties to support access by a UI Automation client to controls
/// that can be set to a value within a range.
/// </summary>
public interface IRangeValueProvider
{
/// <summary>
/// Gets a value that indicates whether the value of a control is read-only.
/// </summary>
bool IsReadOnly { get; }
/// <summary>
/// Gets the minimum range value that is supported by the control.
/// </summary>
double Minimum { get; }
/// <summary>
/// Gets the maximum range value that is supported by the control.
/// </summary>
double Maximum { get; }
/// <summary>
/// Gets the value of the control.
/// </summary>
double Value { get; }
/// <summary>
/// Sets the value of the control.
/// </summary>
/// <param name="value">The value to set.</param>
public void SetValue(double value);
}
}

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

@ -0,0 +1,14 @@
using Avalonia.Automation.Peers;
using Avalonia.Platform;
#nullable enable
namespace Avalonia.Automation.Provider
{
public interface IRootProvider
{
ITopLevelImpl? PlatformImpl { get; }
AutomationPeer? GetFocus();
AutomationPeer? GetPeerFromPoint(Point p);
}
}

71
src/Avalonia.Controls/Automation/Provider/IScrollProvider.cs

@ -0,0 +1,71 @@
namespace Avalonia.Automation.Provider
{
public enum ScrollAmount
{
LargeDecrement,
SmallDecrement,
NoAmount,
LargeIncrement,
SmallIncrement,
}
/// <summary>
/// Exposes methods and properties to support access by a UI Automation client to a control
/// that acts as a scrollable container for a collection of child objects.
/// </summary>
public interface IScrollProvider
{
/// <summary>
/// Gets a value that indicates whether the control can scroll horizontally.
/// </summary>
bool HorizontallyScrollable { get; }
/// <summary>
/// Gets the current horizontal scroll position.
/// </summary>
double HorizontalScrollPercent { get; }
/// <summary>
/// Gets the current horizontal view size.
/// </summary>
double HorizontalViewSize { get; }
/// <summary>
/// Gets a value that indicates whether the control can scroll vertically.
/// </summary>
bool VerticallyScrollable { get; }
/// <summary>
/// Gets the current vertical scroll position.
/// </summary>
double VerticalScrollPercent { get; }
/// <summary>
/// Gets the vertical view size.
/// </summary>
double VerticalViewSize { get; }
/// <summary>
/// Scrolls the visible region of the content area horizontally and vertically.
/// </summary>
/// <param name="horizontalAmount">The horizontal increment specific to the control.</param>
/// <param name="verticalAmount">The vertical increment specific to the control.</param>
void Scroll(ScrollAmount horizontalAmount, ScrollAmount verticalAmount);
/// <summary>
/// Sets the horizontal and vertical scroll position as a percentage of the total content
/// area within the control.
/// </summary>
/// <param name="horizontalPercent">
/// The horizontal position as a percentage of the content area's total range.
/// <see cref="ScrollPatternIdentifiers.NoScroll"/> should be passed in if the control
/// cannot be scrolled in this direction.
/// </param>
/// <param name="verticalPercent">
/// The vertical position as a percentage of the content area's total range.
/// <see cref="ScrollPatternIdentifiers.NoScroll"/> should be passed in if the control
/// cannot be scrolled in this direction.
/// </param>
void SetScrollPercent(double horizontalPercent, double verticalPercent);
}
}

37
src/Avalonia.Controls/Automation/Provider/ISelectionItemProvider .cs

@ -0,0 +1,37 @@
#nullable enable
namespace Avalonia.Automation.Provider
{
/// <summary>
/// Exposes methods and properties to support access by a UI Automation client to individual,
/// selectable child controls of containers that implement <see cref="ISelectionProvider"/>.
/// </summary>
public interface ISelectionItemProvider
{
/// <summary>
/// Gets a value that indicates whether an item is selected.
/// </summary>
bool IsSelected { get; }
/// <summary>
/// Gets the UI Automation provider that implements <see cref="ISelectionProvider"/> and
/// acts as the container for the calling object.
/// </summary>
ISelectionProvider? SelectionContainer { get; }
/// <summary>
/// Adds the current element to the collection of selected items.
/// </summary>
void AddToSelection();
/// <summary>
/// Removes the current element from the collection of selected items.
/// </summary>
void RemoveFromSelection();
/// <summary>
/// Clears any existing selection and then selects the current element.
/// </summary>
void Select();
}
}

29
src/Avalonia.Controls/Automation/Provider/ISelectionProvider.cs

@ -0,0 +1,29 @@
using System.Collections.Generic;
using Avalonia.Automation.Peers;
namespace Avalonia.Automation.Provider
{
/// <summary>
/// Exposes methods and properties to support access by a UI Automation client to controls
/// that act as containers for a collection of individual, selectable child items.
/// </summary>
public interface ISelectionProvider
{
/// <summary>
/// Gets a value that indicates whether the provider allows more than one child element
/// to be selected concurrently.
/// </summary>
bool CanSelectMultiple { get; }
/// <summary>
/// Gets a value that indicates whether the provider requires at least one child element
/// to be selected.
/// </summary>
bool IsSelectionRequired { get; }
/// <summary>
/// Retrieves a provider for each child element that is selected.
/// </summary>
IReadOnlyList<AutomationPeer> GetSelection();
}
}

40
src/Avalonia.Controls/Automation/Provider/IToggleProvider.cs

@ -0,0 +1,40 @@
namespace Avalonia.Automation.Provider
{
/// <summary>
/// Contains values that specify the toggle state of a UI Automation element.
/// </summary>
public enum ToggleState
{
/// <summary>
/// The UI Automation element isn't selected, checked, marked, or otherwise activated.
/// </summary>
Off,
/// <summary>
/// The UI Automation element is selected, checked, marked, or otherwise activated.
/// </summary>
On,
/// <summary>
/// The UI Automation element is in an indeterminate state.
/// </summary>
Indeterminate,
}
/// <summary>
/// Exposes methods and properties to support UI Automation client access to controls that can
/// cycle through a set of states and maintain a particular state.
/// </summary>
public interface IToggleProvider
{
/// <summary>
/// Gets the toggle state of the control.
/// </summary>
ToggleState ToggleState { get; }
/// <summary>
/// Cycles through the toggle states of a control.
/// </summary>
void Toggle();
}
}

31
src/Avalonia.Controls/Automation/Provider/IValueProvider.cs

@ -0,0 +1,31 @@
#nullable enable
namespace Avalonia.Automation.Provider
{
/// <summary>
/// Exposes methods and properties to support access by a UI Automation client to controls
/// that have an intrinsic value that does not span a range and that can be represented as
/// a string.
/// </summary>
public interface IValueProvider
{
/// <summary>
/// Gets a value that indicates whether the value of a control is read-only.
/// </summary>
bool IsReadOnly { get; }
/// <summary>
/// Gets the value of the control.
/// </summary>
public string? Value { get; }
/// <summary>
/// Sets the value of a control.
/// </summary>
/// <param name="value">
/// The value to set. The provider is responsible for converting the value to the
/// appropriate data type.
/// </param>
public void SetValue(string? value);
}
}

30
src/Avalonia.Controls/Automation/RangeValuePatternIdentifiers.cs

@ -0,0 +1,30 @@
using Avalonia.Automation.Provider;
namespace Avalonia.Automation
{
/// <summary>
/// Contains values used as identifiers by <see cref="IRangeValueProvider"/>.
/// </summary>
public static class RangeValuePatternIdentifiers
{
/// <summary>
/// Identifies <see cref="IRangeValueProvider.IsReadOnly"/> automation property.
/// </summary>
public static AutomationProperty IsReadOnlyProperty { get; } = new();
/// <summary>
/// Identifies <see cref="IRangeValueProvider.Minimum"/> automation property.
/// </summary>
public static AutomationProperty MinimumProperty { get; } = new();
/// <summary>
/// Identifies <see cref="IRangeValueProvider.Maximum"/> automation property.
/// </summary>
public static AutomationProperty MaximumProperty { get; } = new();
/// <summary>
/// Identifies <see cref="IRangeValueProvider.Value"/> automation property.
/// </summary>
public static AutomationProperty ValueProperty { get; } = new();
}
}

45
src/Avalonia.Controls/Automation/ScrollPatternIdentifiers.cs

@ -0,0 +1,45 @@
using Avalonia.Automation.Provider;
namespace Avalonia.Automation
{
/// <summary>
/// Contains values used as identifiers by <see cref="IScrollProvider"/>.
/// </summary>
public static class ScrollPatternIdentifiers
{
/// <summary>
/// Specifies that scrolling should not be performed.
/// </summary>
public const double NoScroll = -1;
/// <summary>
/// Identifies <see cref="IScrollProvider.HorizontallyScrollable"/> automation property.
/// </summary>
public static AutomationProperty HorizontallyScrollableProperty { get; } = new();
/// <summary>
/// Identifies <see cref="IScrollProvider.HorizontalScrollPercent"/> automation property.
/// </summary>
public static AutomationProperty HorizontalScrollPercentProperty { get; } = new();
/// <summary>
/// Identifies <see cref="IScrollProvider.HorizontalViewSize"/> automation property.
/// </summary>
public static AutomationProperty HorizontalViewSizeProperty { get; } = new();
/// <summary>
/// Identifies <see cref="IScrollProvider.VerticallyScrollable"/> automation property.
/// </summary>
public static AutomationProperty VerticallyScrollableProperty { get; } = new();
/// <summary>
/// Identifies <see cref="IScrollProvider.VerticalScrollPercent"/> automation property.
/// </summary>
public static AutomationProperty VerticalScrollPercentProperty { get; } = new();
/// <summary>
/// Identifies <see cref="IScrollProvider.VerticalViewSize"/> automation property.
/// </summary>
public static AutomationProperty VerticalViewSizeProperty { get; } = new();
}
}

25
src/Avalonia.Controls/Automation/SelectionPatternIdentifiers.cs

@ -0,0 +1,25 @@
using Avalonia.Automation.Provider;
namespace Avalonia.Automation
{
/// <summary>
/// Contains values used as identifiers by <see cref="ISelectionProvider"/>.
/// </summary>
public static class SelectionPatternIdentifiers
{
/// <summary>
/// Identifies <see cref="ISelectionProvider.CanSelectMultiple"/> automation property.
/// </summary>
public static AutomationProperty CanSelectMultipleProperty { get; } = new();
/// <summary>
/// Identifies <see cref="ISelectionProvider.IsSelectionRequired"/> automation property.
/// </summary>
public static AutomationProperty IsSelectionRequiredProperty { get; } = new();
/// <summary>
/// Identifies the property that gets the selected items in a container.
/// </summary>
public static AutomationProperty SelectionProperty { get; } = new();
}
}

9
src/Avalonia.Controls/Button.cs

@ -1,6 +1,8 @@
using System;
using System.Linq;
using System.Windows.Input;
using Avalonia.Automation.Peers;
using Avalonia.Automation.Platform;
using Avalonia.Controls.Metadata;
using Avalonia.Data;
using Avalonia.Input;
@ -338,6 +340,11 @@ namespace Avalonia.Controls
}
}
protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory)
{
return new ButtonAutomationPeer(factory, this);
}
protected override void UpdateDataValidation<T>(AvaloniaProperty<T> property, BindingValue<T> value)
{
base.UpdateDataValidation(property, value);
@ -354,6 +361,8 @@ namespace Avalonia.Controls
}
}
internal void PerformClick() => OnClick();
/// <summary>
/// Called when the <see cref="Command"/> property changes.
/// </summary>

6
src/Avalonia.Controls/CheckBox.cs

@ -1,3 +1,5 @@
using Avalonia.Automation.Peers;
using Avalonia.Automation.Platform;
using Avalonia.Controls.Primitives;
namespace Avalonia.Controls
@ -7,5 +9,9 @@ namespace Avalonia.Controls
/// </summary>
public class CheckBox : ToggleButton
{
protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory)
{
return new CheckBoxAutomationPeer(factory, this);
}
}
}

7
src/Avalonia.Controls/ComboBox.cs

@ -1,5 +1,7 @@
using System;
using System.Linq;
using Avalonia.Automation.Peers;
using Avalonia.Automation.Platform;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
@ -335,6 +337,11 @@ namespace Avalonia.Controls
_popup.Opened += PopupOpened;
}
protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory)
{
return new ComboBoxAutomationPeer(factory, this);
}
internal void ItemFocused(ComboBoxItem dropDownItem)
{
if (IsDropDownOpen && dropDownItem.IsFocused && dropDownItem.IsArrangeValid)

21
src/Avalonia.Controls/Control.cs

@ -1,5 +1,7 @@
using System;
using System.ComponentModel;
using Avalonia.Automation.Peers;
using Avalonia.Automation.Platform;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
@ -48,6 +50,7 @@ namespace Avalonia.Controls
private DataTemplates? _dataTemplates;
private IControl? _focusAdorner;
private AutomationPeer? _automationPeer;
/// <summary>
/// Gets or sets the control's focus adorner.
@ -188,5 +191,23 @@ namespace Avalonia.Controls
_focusAdorner = null;
}
}
protected virtual AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory)
{
return new NoneAutomationPeer(factory, this);
}
internal AutomationPeer GetOrCreateAutomationPeer(IAutomationNodeFactory factory)
{
VerifyAccess();
if (_automationPeer is object)
{
return _automationPeer;
}
_automationPeer = OnCreateAutomationPeer(factory);
return _automationPeer;
}
}
}

7
src/Avalonia.Controls/Image.cs

@ -1,3 +1,5 @@
using Avalonia.Automation.Peers;
using Avalonia.Automation.Platform;
using Avalonia.Media;
using Avalonia.Media.Imaging;
@ -122,5 +124,10 @@ namespace Avalonia.Controls
return new Size();
}
}
protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory)
{
return new ImageAutomationPeer(factory, this);
}
}
}

7
src/Avalonia.Controls/ItemsControl.cs

@ -3,6 +3,8 @@ using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using Avalonia.Collections;
using Avalonia.Automation.Peers;
using Avalonia.Automation.Platform;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Presenters;
@ -323,6 +325,11 @@ namespace Avalonia.Controls
base.OnKeyDown(e);
}
protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory)
{
return new ItemsControlAutomationPeer(factory, this);
}
/// <summary>
/// Called when the <see cref="Items"/> property changes.
/// </summary>

8
src/Avalonia.Controls/ListBoxItem.cs

@ -1,6 +1,7 @@
using Avalonia.Automation.Peers;
using Avalonia.Automation.Platform;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Mixins;
using Avalonia.Input;
namespace Avalonia.Controls
{
@ -34,5 +35,10 @@ namespace Avalonia.Controls
get { return GetValue(IsSelectedProperty); }
set { SetValue(IsSelectedProperty, value); }
}
protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory)
{
return new ListItemAutomationPeer(factory, this);
}
}
}

7
src/Avalonia.Controls/Menu.cs

@ -1,3 +1,5 @@
using Avalonia.Automation.Peers;
using Avalonia.Automation.Platform;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
@ -92,5 +94,10 @@ namespace Avalonia.Controls
inputRoot.AccessKeyHandler.MainMenu = this;
}
}
protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory)
{
return new MenuAutomationPeer(factory, this);
}
}
}

7
src/Avalonia.Controls/MenuItem.cs

@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows.Input;
using Avalonia.Automation.Peers;
using Avalonia.Automation.Platform;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Mixins;
@ -477,6 +479,11 @@ namespace Avalonia.Controls
}
}
protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory)
{
return new MenuItemAutomationPeer(factory, this);
}
protected override void UpdateDataValidation<T>(AvaloniaProperty<T> property, BindingValue<T> value)
{
base.UpdateDataValidation(property, value);

14
src/Avalonia.Controls/Primitives/Popup.cs

@ -1,6 +1,8 @@
using System;
using System.Linq;
using System.Reactive.Disposables;
using Avalonia.Automation.Peers;
using Avalonia.Automation.Platform;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Input;
@ -596,7 +598,17 @@ namespace Avalonia.Controls.Primitives
{
if (PlacementTarget != null)
{
FocusManager.Instance?.Focus(PlacementTarget);
var e = (IControl?)PlacementTarget;
while (e is object && (!e.Focusable || !e.IsEffectivelyEnabled || !e.IsVisible))
{
e = e.Parent;
}
if (e is object)
{
FocusManager.Instance?.Focus(e);
}
}
else
{

7
src/Avalonia.Controls/Primitives/PopupRoot.cs

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using Avalonia.Automation.Peers;
using Avalonia.Automation.Platform;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Interactivity;
using Avalonia.Media;
@ -168,5 +170,10 @@ namespace Avalonia.Controls.Primitives
return ClientSize;
}
}
protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory)
{
return new PopupRootAutomationPeer(factory, this);
}
}
}

7
src/Avalonia.Controls/Primitives/ToggleButton.cs

@ -1,4 +1,6 @@
using System;
using Avalonia.Automation.Peers;
using Avalonia.Automation.Platform;
using Avalonia.Controls.Metadata;
using Avalonia.Data;
using Avalonia.Interactivity;
@ -169,6 +171,11 @@ namespace Avalonia.Controls.Primitives
RaiseEvent(e);
}
protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory)
{
return new ToggleButtonAutomationPeer(factory, this);
}
private void OnIsCheckedChanged(AvaloniaPropertyChangedEventArgs e)
{
var newValue = (bool?)e.NewValue;

7
src/Avalonia.Controls/ScrollViewer.cs

@ -1,5 +1,7 @@
using System;
using System.Reactive.Linq;
using Avalonia.Automation.Peers;
using Avalonia.Automation.Platform;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
@ -688,6 +690,11 @@ namespace Avalonia.Controls
_scrollBarExpandSubscription = SubscribeToScrollBars(e);
}
protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory)
{
return new ScrollViewerAutomationPeer(factory, this);
}
private IDisposable SubscribeToScrollBars(TemplateAppliedEventArgs e)
{
static IObservable<bool> GetExpandedObservable(ScrollBar scrollBar)

7
src/Avalonia.Controls/Slider.cs

@ -1,5 +1,7 @@
using System;
using Avalonia.Collections;
using Avalonia.Automation.Peers;
using Avalonia.Automation.Platform;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Mixins;
using Avalonia.Controls.Primitives;
@ -209,6 +211,11 @@ namespace Avalonia.Controls
_pointerMovedDispose = this.AddDisposableHandler(PointerMovedEvent, TrackMoved, RoutingStrategies.Tunnel);
}
protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory)
{
return new SliderAutomationPeer(factory, this);
}
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);

7
src/Avalonia.Controls/TabControl.cs

@ -1,6 +1,8 @@
using System.ComponentModel;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Automation.Peers;
using Avalonia.Automation.Platform;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
@ -230,5 +232,10 @@ namespace Avalonia.Controls
}
}
}
protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory)
{
return new TabControlAutomationPeer(factory, this);
}
}
}

7
src/Avalonia.Controls/TabItem.cs

@ -1,3 +1,5 @@
using Avalonia.Automation.Peers;
using Avalonia.Automation.Platform;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Mixins;
using Avalonia.Controls.Primitives;
@ -80,5 +82,10 @@ namespace Avalonia.Controls
}
}
}
protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory)
{
return new ListItemAutomationPeer(factory, this);
}
}
}

9
src/Avalonia.Controls/TextBlock.cs

@ -1,9 +1,11 @@
using System.Reactive.Linq;
using Avalonia.Automation.Peers;
using Avalonia.Automation.Platform;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Metadata;
using Avalonia.Layout;
namespace Avalonia.Controls
{
@ -532,6 +534,11 @@ namespace Avalonia.Controls
InvalidateMeasure();
}
protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory)
{
return new TextAutomationPeer(factory, this);
}
private static bool IsValidMaxLines(int maxLines) => maxLines >= 0;
private static bool IsValidLineHeight(double lineHeight) => double.IsNaN(lineHeight) || lineHeight > 0;

7
src/Avalonia.Controls/TextBox.cs

@ -14,6 +14,8 @@ using Avalonia.Data;
using Avalonia.Layout;
using Avalonia.Utilities;
using Avalonia.Controls.Metadata;
using Avalonia.Automation.Peers;
using Avalonia.Automation.Platform;
namespace Avalonia.Controls
{
@ -904,6 +906,11 @@ namespace Avalonia.Controls
}
}
protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory)
{
return new TextBoxAutomationPeer(factory, this);
}
protected override void UpdateDataValidation<T>(AvaloniaProperty<T> property, BindingValue<T> value)
{
if (property == TextProperty)

7
src/Avalonia.Controls/Window.cs

@ -4,6 +4,8 @@ using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Avalonia.Automation.Peers;
using Avalonia.Automation.Platform;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Interactivity;
@ -965,5 +967,10 @@ namespace Avalonia.Controls
}
}
}
protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory)
{
return new WindowAutomationPeer(factory, this);
}
}
}

32
src/Avalonia.Input/KeyboardDevice.cs

@ -24,30 +24,7 @@ namespace Avalonia.Input
// the source of truth about the input focus is in KeyboardDevice
private readonly TextInputMethodManager _textInputManager = new TextInputMethodManager();
public IInputElement? FocusedElement
{
get
{
return _focusedElement;
}
private set
{
_focusedElement = value;
if (_focusedElement != null && _focusedElement.IsAttachedToVisualTree)
{
_focusedRoot = _focusedElement.VisualRoot as IInputRoot;
}
else
{
_focusedRoot = null;
}
RaisePropertyChanged();
_textInputManager.SetFocusedElement(value);
}
}
public IInputElement? FocusedElement => _focusedElement;
private void ClearFocusWithinAncestors(IInputElement? element)
{
@ -162,8 +139,8 @@ namespace Avalonia.Input
}
SetIsFocusWithin(FocusedElement, element);
FocusedElement = element;
_focusedElement = element;
_focusedRoot = _focusedElement?.VisualRoot as IInputRoot;
interactive?.RaiseEvent(new RoutedEventArgs
{
@ -178,6 +155,9 @@ namespace Avalonia.Input
NavigationMethod = method,
KeyModifiers = keyModifiers,
});
_textInputManager.SetFocusedElement(element);
RaisePropertyChanged(nameof(FocusedElement));
}
}

19
src/Windows/Avalonia.Win32/Automation/AutomationNode.ExpandCollapse.cs

@ -0,0 +1,19 @@
using Avalonia.Automation;
using Avalonia.Automation.Provider;
using UIA = Avalonia.Win32.Interop.Automation;
#nullable enable
namespace Avalonia.Win32.Automation
{
internal partial class AutomationNode : UIA.IExpandCollapseProvider
{
public ExpandCollapseState ExpandCollapseState
{
get => InvokeSync<IExpandCollapseProvider, ExpandCollapseState>(x => x.ExpandCollapseState);
}
public void Expand() => InvokeSync<IExpandCollapseProvider>(x => x.Expand());
public void Collapse() => InvokeSync<IExpandCollapseProvider>(x => x.Collapse());
}
}

19
src/Windows/Avalonia.Win32/Automation/AutomationNode.RangeValue.cs

@ -0,0 +1,19 @@
using Avalonia.Automation.Provider;
using UIA = Avalonia.Win32.Interop.Automation;
#nullable enable
namespace Avalonia.Win32.Automation
{
internal partial class AutomationNode : UIA.IRangeValueProvider
{
double UIA.IRangeValueProvider.Value => InvokeSync<IRangeValueProvider, double>(x => x.Value);
public bool IsReadOnly => InvokeSync<IRangeValueProvider, bool>(x => x.IsReadOnly);
public double Maximum => InvokeSync<IRangeValueProvider, double>(x => x.Maximum);
public double Minimum => InvokeSync<IRangeValueProvider, double>(x => x.Minimum);
public double LargeChange => 1;
public double SmallChange => 1;
public void SetValue(double value) => InvokeSync<IRangeValueProvider>(x => x.SetValue(value));
}
}

32
src/Windows/Avalonia.Win32/Automation/AutomationNode.Scroll.cs

@ -0,0 +1,32 @@
using Avalonia.Automation.Provider;
using UIA = Avalonia.Win32.Interop.Automation;
#nullable enable
namespace Avalonia.Win32.Automation
{
internal partial class AutomationNode : UIA.IScrollProvider, UIA.IScrollItemProvider
{
public bool HorizontallyScrollable => InvokeSync<IScrollProvider, bool>(x => x.HorizontallyScrollable);
public double HorizontalScrollPercent => InvokeSync<IScrollProvider, double>(x => x.HorizontalScrollPercent);
public double HorizontalViewSize => InvokeSync<IScrollProvider, double>(x => x.HorizontalViewSize);
public bool VerticallyScrollable => InvokeSync<IScrollProvider, bool>(x => x.VerticallyScrollable);
public double VerticalScrollPercent => InvokeSync<IScrollProvider, double>(x => x.VerticalScrollPercent);
public double VerticalViewSize => InvokeSync<IScrollProvider, double>(x => x.VerticalViewSize);
public void Scroll(ScrollAmount horizontalAmount, ScrollAmount verticalAmount)
{
InvokeSync<IScrollProvider>(x => x.Scroll(horizontalAmount, verticalAmount));
}
public void SetScrollPercent(double horizontalPercent, double verticalPercent)
{
InvokeSync<IScrollProvider>(x => x.SetScrollPercent(horizontalPercent, verticalPercent));
}
public void ScrollIntoView()
{
InvokeSync(() => Peer.BringIntoView());
}
}
}

38
src/Windows/Avalonia.Win32/Automation/AutomationNode.Selection.cs

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Automation.Peers;
using Avalonia.Automation.Provider;
using UIA = Avalonia.Win32.Interop.Automation;
#nullable enable
namespace Avalonia.Win32.Automation
{
internal partial class AutomationNode : UIA.ISelectionProvider, UIA.ISelectionItemProvider
{
public bool CanSelectMultiple => InvokeSync<ISelectionProvider, bool>(x => x.CanSelectMultiple);
public bool IsSelectionRequired => InvokeSync<ISelectionProvider, bool>(x => x.IsSelectionRequired);
public bool IsSelected => InvokeSync<ISelectionItemProvider, bool>(x => x.IsSelected);
public UIA.IRawElementProviderSimple? SelectionContainer
{
get
{
var peer = InvokeSync<ISelectionItemProvider, ISelectionProvider?>(x => x.SelectionContainer);
return (peer as AutomationPeer)?.Node as AutomationNode;
}
}
public UIA.IRawElementProviderSimple[] GetSelection()
{
var peers = InvokeSync<ISelectionProvider, IReadOnlyList<AutomationPeer>>(x => x.GetSelection());
return peers?.Select(x => (UIA.IRawElementProviderSimple)x.Node).ToArray() ??
Array.Empty<UIA.IRawElementProviderSimple>();
}
public void AddToSelection() => InvokeSync<ISelectionItemProvider>(x => x.AddToSelection());
public void RemoveFromSelection() => InvokeSync<ISelectionItemProvider>(x => x.RemoveFromSelection());
public void Select() => InvokeSync<ISelectionItemProvider>(x => x.Select());
}
}

13
src/Windows/Avalonia.Win32/Automation/AutomationNode.Toggle.cs

@ -0,0 +1,13 @@
using Avalonia.Automation.Provider;
using UIA = Avalonia.Win32.Interop.Automation;
#nullable enable
namespace Avalonia.Win32.Automation
{
internal partial class AutomationNode : UIA.IToggleProvider
{
public ToggleState ToggleState => InvokeSync<IToggleProvider, ToggleState>(x => x.ToggleState);
public void Toggle() => InvokeSync<IToggleProvider>(x => x.Toggle());
}
}

18
src/Windows/Avalonia.Win32/Automation/AutomationNode.Value.cs

@ -0,0 +1,18 @@
using System.Runtime.InteropServices;
using Avalonia.Automation.Provider;
using UIA = Avalonia.Win32.Interop.Automation;
#nullable enable
namespace Avalonia.Win32.Automation
{
internal partial class AutomationNode : UIA.IValueProvider
{
public string? Value => InvokeSync<IValueProvider, string?>(x => x.Value);
public void SetValue([MarshalAs(UnmanagedType.LPWStr)] string? value)
{
InvokeSync<IValueProvider>(x => x.SetValue(value));
}
}
}

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

@ -0,0 +1,328 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using Avalonia.Automation;
using Avalonia.Automation.Peers;
using Avalonia.Automation.Platform;
using Avalonia.Threading;
using Avalonia.Win32.Interop.Automation;
using AAP = Avalonia.Automation.Provider;
#nullable enable
namespace Avalonia.Win32.Automation
{
[ComVisible(true)]
internal partial class AutomationNode : MarshalByRefObject,
IAutomationNode,
IRawElementProviderSimple,
IRawElementProviderSimple2,
IRawElementProviderFragment,
IRawElementProviderAdviseEvents,
IInvokeProvider
{
private static Dictionary<AutomationProperty, UiaPropertyId> s_propertyMap = new()
{
{ AutomationElementIdentifiers.BoundingRectangleProperty, UiaPropertyId.BoundingRectangle },
{ AutomationElementIdentifiers.ClassNameProperty, UiaPropertyId.ClassName },
{ AutomationElementIdentifiers.NameProperty, UiaPropertyId.Name },
{ ExpandCollapsePatternIdentifiers.ExpandCollapseStateProperty, UiaPropertyId.ExpandCollapseExpandCollapseState },
{ RangeValuePatternIdentifiers.IsReadOnlyProperty, UiaPropertyId.RangeValueIsReadOnly},
{ RangeValuePatternIdentifiers.MaximumProperty, UiaPropertyId.RangeValueMaximum },
{ RangeValuePatternIdentifiers.MinimumProperty, UiaPropertyId.RangeValueMinimum },
{ RangeValuePatternIdentifiers.ValueProperty, UiaPropertyId.RangeValueValue },
{ ScrollPatternIdentifiers.HorizontallyScrollableProperty, UiaPropertyId.ScrollHorizontallyScrollable },
{ ScrollPatternIdentifiers.HorizontalScrollPercentProperty, UiaPropertyId.ScrollHorizontalScrollPercent },
{ ScrollPatternIdentifiers.HorizontalViewSizeProperty, UiaPropertyId.ScrollHorizontalViewSize },
{ ScrollPatternIdentifiers.VerticallyScrollableProperty, UiaPropertyId.ScrollVerticallyScrollable },
{ ScrollPatternIdentifiers.VerticalScrollPercentProperty, UiaPropertyId.ScrollVerticalScrollPercent },
{ ScrollPatternIdentifiers.VerticalViewSizeProperty, UiaPropertyId.ScrollVerticalViewSize },
{ SelectionPatternIdentifiers.CanSelectMultipleProperty, UiaPropertyId.SelectionCanSelectMultiple },
{ SelectionPatternIdentifiers.IsSelectionRequiredProperty, UiaPropertyId.SelectionIsSelectionRequired },
{ SelectionPatternIdentifiers.SelectionProperty, UiaPropertyId.SelectionSelection },
};
private readonly int[] _runtimeId;
private int _raiseFocusChanged;
private int _raisePropertyChanged;
public AutomationNode(AutomationPeer peer)
{
_runtimeId = new int[] { 3, GetHashCode() };
Peer = peer;
}
public AutomationPeer Peer { get; }
public IAutomationNodeFactory Factory => AutomationNodeFactory.Instance;
public Rect BoundingRectangle
{
get => InvokeSync(() =>
{
if (GetRoot()?.Node is RootAutomationNode root)
return root.ToScreen(Peer.GetBoundingRectangle());
return default;
});
}
public virtual IRawElementProviderFragmentRoot? FragmentRoot
{
get => InvokeSync(() => GetRoot())?.Node as IRawElementProviderFragmentRoot;
}
public virtual IRawElementProviderSimple? HostRawElementProvider => null;
public ProviderOptions ProviderOptions => ProviderOptions.ServerSideProvider;
public void ChildrenChanged()
{
UiaCoreProviderApi.UiaRaiseStructureChangedEvent(
this,
StructureChangeType.ChildrenInvalidated,
_runtimeId,
_runtimeId.Length);
}
public void PropertyChanged(AutomationProperty property, object? oldValue, object? newValue)
{
if (_raisePropertyChanged > 0 && s_propertyMap.TryGetValue(property, out var id))
{
UiaCoreProviderApi.UiaRaiseAutomationPropertyChangedEvent(this, (int)id, oldValue, newValue);
}
}
[return: MarshalAs(UnmanagedType.IUnknown)]
public virtual object? GetPatternProvider(int patternId)
{
return (UiaPatternId)patternId switch
{
UiaPatternId.ExpandCollapse => Peer is IExpandCollapseProvider ? this : null,
UiaPatternId.Invoke => Peer is AAP.IInvokeProvider ? this : null,
UiaPatternId.RangeValue => Peer is AAP.IRangeValueProvider ? this : null,
UiaPatternId.Scroll => Peer is AAP.IScrollProvider ? this : null,
UiaPatternId.ScrollItem => this,
UiaPatternId.Selection => Peer is AAP.ISelectionProvider ? this : null,
UiaPatternId.SelectionItem => Peer is AAP.ISelectionItemProvider ? this : null,
UiaPatternId.Toggle => Peer is AAP.IToggleProvider ? this : null,
UiaPatternId.Value => Peer is AAP.IValueProvider ? this : null,
_ => null,
};
}
public virtual object? GetPropertyValue(int propertyId)
{
return (UiaPropertyId)propertyId switch
{
UiaPropertyId.AutomationId => InvokeSync(() => Peer.GetAutomationId()),
UiaPropertyId.ClassName => InvokeSync(() => Peer.GetClassName()),
UiaPropertyId.ClickablePoint => new[] { BoundingRectangle.Center.X, BoundingRectangle.Center.Y },
UiaPropertyId.ControlType => InvokeSync(() => ToUiaControlType(Peer.GetAutomationControlType())),
UiaPropertyId.Culture => CultureInfo.CurrentCulture.LCID,
UiaPropertyId.FrameworkId => "Avalonia",
UiaPropertyId.HasKeyboardFocus => InvokeSync(() => Peer.HasKeyboardFocus()),
UiaPropertyId.IsContentElement => InvokeSync(() => Peer.IsContentElement()),
UiaPropertyId.IsControlElement => InvokeSync(() => Peer.IsControlElement()),
UiaPropertyId.IsEnabled => InvokeSync(() => Peer.IsEnabled()),
UiaPropertyId.IsKeyboardFocusable => InvokeSync(() => Peer.IsKeyboardFocusable()),
UiaPropertyId.LocalizedControlType => InvokeSync(() => Peer.GetLocalizedControlType()),
UiaPropertyId.Name => InvokeSync(() => Peer.GetName()),
UiaPropertyId.ProcessId => Process.GetCurrentProcess().Id,
UiaPropertyId.RuntimeId => _runtimeId,
_ => null,
};
}
public int[]? GetRuntimeId() => _runtimeId;
public virtual IRawElementProviderFragment? Navigate(NavigateDirection direction)
{
IAutomationNode? GetSibling(int direction)
{
var children = Peer.GetParent()?.GetChildren();
for (var i = 0; i < (children?.Count ?? 0); ++i)
{
if (ReferenceEquals(children![i], Peer))
{
var j = i + direction;
if (j >= 0 && j < children.Count)
return children[j].Node;
}
}
return null;
}
return InvokeSync(() =>
{
return direction switch
{
NavigateDirection.Parent => Peer.GetParent()?.Node,
NavigateDirection.NextSibling => GetSibling(1),
NavigateDirection.PreviousSibling => GetSibling(-1),
NavigateDirection.FirstChild => Peer.GetChildren().FirstOrDefault()?.Node,
NavigateDirection.LastChild => Peer.GetChildren().LastOrDefault()?.Node,
_ => null,
};
}) as IRawElementProviderFragment;
}
public void SetFocus() => InvokeSync(() => Peer.SetFocus());
IRawElementProviderSimple[]? IRawElementProviderFragment.GetEmbeddedFragmentRoots() => null;
void IRawElementProviderSimple2.ShowContextMenu() => InvokeSync(() => Peer.ShowContextMenu());
void IInvokeProvider.Invoke() => InvokeSync((AAP.IInvokeProvider x) => x.Invoke());
void IRawElementProviderAdviseEvents.AdviseEventAdded(int eventId, int[] properties)
{
switch ((UiaEventId)eventId)
{
case UiaEventId.AutomationPropertyChanged:
++_raisePropertyChanged;
break;
case UiaEventId.AutomationFocusChanged:
++_raiseFocusChanged;
break;
}
}
void IRawElementProviderAdviseEvents.AdviseEventRemoved(int eventId, int[] properties)
{
switch ((UiaEventId)eventId)
{
case UiaEventId.AutomationPropertyChanged:
--_raisePropertyChanged;
break;
case UiaEventId.AutomationFocusChanged:
--_raiseFocusChanged;
break;
}
}
protected void InvokeSync(Action action)
{
if (Dispatcher.UIThread.CheckAccess())
action();
else
Dispatcher.UIThread.InvokeAsync(action).Wait();
}
[return: MaybeNull]
protected T InvokeSync<T>(Func<T> func)
{
if (Dispatcher.UIThread.CheckAccess())
return func();
else
return Dispatcher.UIThread.InvokeAsync(func).Result;
}
protected void InvokeSync<TInterface>(Action<TInterface> action)
{
if (Peer is TInterface i)
{
try
{
InvokeSync(() => action(i));
}
catch (AggregateException e) when (e.InnerException is ElementNotEnabledException)
{
throw new COMException(e.Message, UiaCoreProviderApi.UIA_E_ELEMENTNOTENABLED);
}
}
}
[return: MaybeNull]
protected TResult InvokeSync<TInterface, TResult>(Func<TInterface, TResult> func)
{
if (Peer is TInterface i)
{
try
{
return InvokeSync(() => func(i));
}
catch (AggregateException e) when (e.InnerException is ElementNotEnabledException)
{
throw new COMException(e.Message, UiaCoreProviderApi.UIA_E_ELEMENTNOTENABLED);
}
}
return default;
}
protected void RaiseFocusChanged(AutomationNode? focused)
{
if (_raiseFocusChanged > 0)
{
UiaCoreProviderApi.UiaRaiseAutomationEvent(
focused,
(int)UiaEventId.AutomationFocusChanged);
}
}
private AutomationPeer GetRoot()
{
Dispatcher.UIThread.VerifyAccess();
var peer = Peer;
var parent = peer.GetParent();
while (parent is object)
{
peer = parent;
parent = peer.GetParent();
}
return peer;
}
private static UiaControlTypeId ToUiaControlType(AutomationControlType role)
{
return role switch
{
AutomationControlType.Button => UiaControlTypeId.Button,
AutomationControlType.Calendar => UiaControlTypeId.Calendar,
AutomationControlType.CheckBox => UiaControlTypeId.CheckBox,
AutomationControlType.ComboBox => UiaControlTypeId.ComboBox,
AutomationControlType.Edit => UiaControlTypeId.Edit,
AutomationControlType.Hyperlink => UiaControlTypeId.Hyperlink,
AutomationControlType.Image => UiaControlTypeId.Image,
AutomationControlType.ListItem => UiaControlTypeId.ListItem,
AutomationControlType.List => UiaControlTypeId.List,
AutomationControlType.Menu => UiaControlTypeId.Menu,
AutomationControlType.MenuBar => UiaControlTypeId.MenuBar,
AutomationControlType.MenuItem => UiaControlTypeId.MenuItem,
AutomationControlType.ProgressBar => UiaControlTypeId.ProgressBar,
AutomationControlType.RadioButton => UiaControlTypeId.RadioButton,
AutomationControlType.ScrollBar => UiaControlTypeId.ScrollBar,
AutomationControlType.Slider => UiaControlTypeId.Slider,
AutomationControlType.Spinner => UiaControlTypeId.Spinner,
AutomationControlType.StatusBar => UiaControlTypeId.StatusBar,
AutomationControlType.Tab => UiaControlTypeId.Tab,
AutomationControlType.TabItem => UiaControlTypeId.TabItem,
AutomationControlType.Text => UiaControlTypeId.Text,
AutomationControlType.ToolBar => UiaControlTypeId.ToolBar,
AutomationControlType.ToolTip => UiaControlTypeId.ToolTip,
AutomationControlType.Tree => UiaControlTypeId.Tree,
AutomationControlType.TreeItem => UiaControlTypeId.TreeItem,
AutomationControlType.Custom => UiaControlTypeId.Custom,
AutomationControlType.Group => UiaControlTypeId.Group,
AutomationControlType.Thumb => UiaControlTypeId.Thumb,
AutomationControlType.DataGrid => UiaControlTypeId.DataGrid,
AutomationControlType.DataItem => UiaControlTypeId.DataItem,
AutomationControlType.Document => UiaControlTypeId.Document,
AutomationControlType.SplitButton => UiaControlTypeId.SplitButton,
AutomationControlType.Window => UiaControlTypeId.Window,
AutomationControlType.Pane => UiaControlTypeId.Pane,
AutomationControlType.Header => UiaControlTypeId.Header,
AutomationControlType.HeaderItem => UiaControlTypeId.HeaderItem,
AutomationControlType.Table => UiaControlTypeId.Table,
AutomationControlType.TitleBar => UiaControlTypeId.TitleBar,
AutomationControlType.Separator => UiaControlTypeId.Separator,
_ => UiaControlTypeId.Custom,
};
}
}
}

20
src/Windows/Avalonia.Win32/Automation/AutomationNodeFactory.cs

@ -0,0 +1,20 @@
using Avalonia.Automation.Peers;
using Avalonia.Automation.Platform;
using Avalonia.Automation.Provider;
using Avalonia.Threading;
#nullable enable
namespace Avalonia.Win32.Automation
{
internal class AutomationNodeFactory : IAutomationNodeFactory
{
public static readonly AutomationNodeFactory Instance = new AutomationNodeFactory();
public IAutomationNode CreateNode(AutomationPeer peer)
{
Dispatcher.UIThread.VerifyAccess();
return peer is IRootProvider ? new RootAutomationNode(peer) : new AutomationNode(peer);
}
}
}

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

@ -0,0 +1,72 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.Automation.Peers;
using Avalonia.Automation.Platform;
using Avalonia.Automation.Provider;
using Avalonia.Win32.Interop.Automation;
#nullable enable
namespace Avalonia.Win32.Automation
{
internal class RootAutomationNode : AutomationNode,
IRawElementProviderFragmentRoot,
IRootAutomationNode
{
public RootAutomationNode(AutomationPeer peer)
: base(peer)
{
}
public override IRawElementProviderFragmentRoot? FragmentRoot => this;
public new IRootProvider Peer => (IRootProvider)base.Peer;
public WindowImpl? WindowImpl => Peer.PlatformImpl as WindowImpl;
public IRawElementProviderFragment? ElementProviderFromPoint(double x, double y)
{
if (WindowImpl is null)
return null;
var p = WindowImpl.PointToClient(new PixelPoint((int)x, (int)y));
var peer = (WindowBaseAutomationPeer)Peer;
var found = InvokeSync(() => peer.GetPeerFromPoint(p));
var result = found?.Node as IRawElementProviderFragment;
return result;
}
public IRawElementProviderFragment? GetFocus()
{
var focus = InvokeSync(() => Peer.GetFocus());
return (AutomationNode?)focus?.Node;
}
public void FocusChanged(AutomationPeer? focus)
{
var node = focus?.Node as AutomationNode;
RaiseFocusChanged(node);
}
public Rect ToScreen(Rect rect)
{
if (WindowImpl is null)
return default;
return new PixelRect(
WindowImpl.PointToScreen(rect.TopLeft),
WindowImpl.PointToScreen(rect.BottomRight))
.ToRect(1);
}
public override IRawElementProviderSimple? HostRawElementProvider
{
get
{
var handle = WindowImpl?.Handle.Handle ?? IntPtr.Zero;
if (handle == IntPtr.Zero)
return null;
var hr = UiaCoreProviderApi.UiaHostProviderFromHwnd(handle, out var result);
Marshal.ThrowExceptionForHR(hr);
return result;
}
}
}
}

3
src/Windows/Avalonia.Win32/Avalonia.Win32.csproj

@ -6,6 +6,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
<Compile Include="..\..\Avalonia.Base\Metadata\NullableAttributes.cs" Link="NullableAttributes.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" />
<AvnComIdl Include="WinRT\winrt.idl" OutputFile="WinRT\WinRT.Generated.cs" />

26
src/Windows/Avalonia.Win32/Interop/Automation/IDockProvider.cs

@ -0,0 +1,26 @@
using System;
using System.Runtime.InteropServices;
namespace Avalonia.Win32.Interop.Automation
{
[ComVisible(true)]
[Guid("70d46e77-e3a8-449d-913c-e30eb2afecdb")]
public enum DockPosition
{
Top,
Left,
Bottom,
Right,
Fill,
None
}
[ComVisible(true)]
[Guid("159bc72c-4ad3-485e-9637-d7052edf0146")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IDockProvider
{
void SetDockPosition(DockPosition dockPosition);
DockPosition DockPosition { get; }
}
}

16
src/Windows/Avalonia.Win32/Interop/Automation/IExpandCollapseProvider.cs

@ -0,0 +1,16 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.Automation;
namespace Avalonia.Win32.Interop.Automation
{
[ComVisible(true)]
[Guid("d847d3a5-cab0-4a98-8c32-ecb45c59ad24")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IExpandCollapseProvider
{
void Expand();
void Collapse();
ExpandCollapseState ExpandCollapseState { get; }
}
}

17
src/Windows/Avalonia.Win32/Interop/Automation/IGridItemProvider.cs

@ -0,0 +1,17 @@
using System;
using System.Runtime.InteropServices;
namespace Avalonia.Win32.Interop.Automation
{
[ComVisible(true)]
[Guid("d02541f1-fb81-4d64-ae32-f520f8a6dbd1")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IGridItemProvider
{
int Row { get; }
int Column { get; }
int RowSpan { get; }
int ColumnSpan { get; }
IRawElementProviderSimple ContainingGrid { get; }
}
}

15
src/Windows/Avalonia.Win32/Interop/Automation/IGridProvider.cs

@ -0,0 +1,15 @@
using System;
using System.Runtime.InteropServices;
namespace Avalonia.Win32.Interop.Automation
{
[ComVisible(true)]
[Guid("b17d6187-0907-464b-a168-0ef17a1572b1")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IGridProvider
{
IRawElementProviderSimple GetItem(int row, int column);
int RowCount { get; }
int ColumnCount { get; }
}
}

19
src/Windows/Avalonia.Win32/Interop/Automation/IInvokeProvider.cs

@ -0,0 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Description: Invoke pattern provider interface
using System;
using System.Runtime.InteropServices;
namespace Avalonia.Win32.Interop.Automation
{
[ComVisible(true)]
[Guid("54fcb24b-e18e-47a2-b4d3-eccbe77599a2")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IInvokeProvider
{
void Invoke();
}
}

16
src/Windows/Avalonia.Win32/Interop/Automation/IMultipleViewProvider.cs

@ -0,0 +1,16 @@
using System;
using System.Runtime.InteropServices;
namespace Avalonia.Win32.Interop.Automation
{
[ComVisible(true)]
[Guid("6278cab1-b556-4a1a-b4e0-418acc523201")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IMultipleViewProvider
{
string GetViewName(int viewId);
void SetCurrentView(int viewId);
int CurrentView { get; }
int[] GetSupportedViews();
}
}

19
src/Windows/Avalonia.Win32/Interop/Automation/IRangeValueProvider.cs

@ -0,0 +1,19 @@
using System;
using System.Runtime.InteropServices;
namespace Avalonia.Win32.Interop.Automation
{
[ComVisible(true)]
[Guid("36dc7aef-33e6-4691-afe1-2be7274b3d33")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IRangeValueProvider
{
void SetValue(double value);
double Value { get; }
bool IsReadOnly { [return: MarshalAs(UnmanagedType.Bool)] get; }
double Maximum { get; }
double Minimum { get; }
double LargeChange { get; }
double SmallChange { get; }
}
}

15
src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderAdviseEvents.cs

@ -0,0 +1,15 @@
using System;
using System.Runtime.InteropServices;
namespace Avalonia.Win32.Interop.Automation
{
[ComVisible(true)]
[Guid("a407b27b-0f6d-4427-9292-473c7bf93258")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IRawElementProviderAdviseEvents : IRawElementProviderSimple
{
void AdviseEventAdded(int eventId, int [] properties);
void AdviseEventRemoved(int eventId, int [] properties);
}
}

34
src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderFragment.cs

@ -0,0 +1,34 @@
using System;
using System.Runtime.InteropServices;
#nullable enable
namespace Avalonia.Win32.Interop.Automation
{
[ComVisible(true)]
[Guid("670c3006-bf4c-428b-8534-e1848f645122")]
public enum NavigateDirection
{
Parent,
NextSibling,
PreviousSibling,
FirstChild,
LastChild,
}
// NOTE: This interface needs to be public otherwise Navigate is never called. I have no idea
// why given that IRawElementProviderSimple and IRawElementProviderFragmentRoot seem to get
// called fine when they're internal, but I lost a couple of days to this.
[ComVisible(true)]
[Guid("f7063da8-8359-439c-9297-bbc5299a7d87")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IRawElementProviderFragment : IRawElementProviderSimple
{
IRawElementProviderFragment? Navigate(NavigateDirection direction);
int[]? GetRuntimeId();
Rect BoundingRectangle { get; }
IRawElementProviderSimple[]? GetEmbeddedFragmentRoots();
void SetFocus();
IRawElementProviderFragmentRoot? FragmentRoot { get; }
}
}

14
src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderFragmentRoot.cs

@ -0,0 +1,14 @@
using System;
using System.Runtime.InteropServices;
namespace Avalonia.Win32.Interop.Automation
{
[ComVisible(true)]
[Guid("620ce2a5-ab8f-40a9-86cb-de3c75599b58")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IRawElementProviderFragmentRoot : IRawElementProviderFragment
{
IRawElementProviderFragment ElementProviderFromPoint(double x, double y);
IRawElementProviderFragment GetFocus();
}
}

285
src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderSimple.cs

@ -0,0 +1,285 @@
using System;
using System.Runtime.InteropServices;
#nullable enable
namespace Avalonia.Win32.Interop.Automation
{
[Flags]
public enum ProviderOptions
{
ClientSideProvider = 0x0001,
ServerSideProvider = 0x0002,
NonClientAreaProvider = 0x0004,
OverrideProvider = 0x0008,
ProviderOwnsSetFocus = 0x0010,
UseComThreading = 0x0020
}
internal enum UiaPropertyId
{
RuntimeId = 30000,
BoundingRectangle,
ProcessId,
ControlType,
LocalizedControlType,
Name,
AcceleratorKey,
AccessKey,
HasKeyboardFocus,
IsKeyboardFocusable,
IsEnabled,
AutomationId,
ClassName,
HelpText,
ClickablePoint,
Culture,
IsControlElement,
IsContentElement,
LabeledBy,
IsPassword,
NativeWindowHandle,
ItemType,
IsOffscreen,
Orientation,
FrameworkId,
IsRequiredForForm,
ItemStatus,
IsDockPatternAvailable,
IsExpandCollapsePatternAvailable,
IsGridItemPatternAvailable,
IsGridPatternAvailable,
IsInvokePatternAvailable,
IsMultipleViewPatternAvailable,
IsRangeValuePatternAvailable,
IsScrollPatternAvailable,
IsScrollItemPatternAvailable,
IsSelectionItemPatternAvailable,
IsSelectionPatternAvailable,
IsTablePatternAvailable,
IsTableItemPatternAvailable,
IsTextPatternAvailable,
IsTogglePatternAvailable,
IsTransformPatternAvailable,
IsValuePatternAvailable,
IsWindowPatternAvailable,
ValueValue,
ValueIsReadOnly,
RangeValueValue,
RangeValueIsReadOnly,
RangeValueMinimum,
RangeValueMaximum,
RangeValueLargeChange,
RangeValueSmallChange,
ScrollHorizontalScrollPercent,
ScrollHorizontalViewSize,
ScrollVerticalScrollPercent,
ScrollVerticalViewSize,
ScrollHorizontallyScrollable,
ScrollVerticallyScrollable,
SelectionSelection,
SelectionCanSelectMultiple,
SelectionIsSelectionRequired,
GridRowCount,
GridColumnCount,
GridItemRow,
GridItemColumn,
GridItemRowSpan,
GridItemColumnSpan,
GridItemContainingGrid,
DockDockPosition,
ExpandCollapseExpandCollapseState,
MultipleViewCurrentView,
MultipleViewSupportedViews,
WindowCanMaximize,
WindowCanMinimize,
WindowWindowVisualState,
WindowWindowInteractionState,
WindowIsModal,
WindowIsTopmost,
SelectionItemIsSelected,
SelectionItemSelectionContainer,
TableRowHeaders,
TableColumnHeaders,
TableRowOrColumnMajor,
TableItemRowHeaderItems,
TableItemColumnHeaderItems,
ToggleToggleState,
TransformCanMove,
TransformCanResize,
TransformCanRotate,
IsLegacyIAccessiblePatternAvailable,
LegacyIAccessibleChildId,
LegacyIAccessibleName,
LegacyIAccessibleValue,
LegacyIAccessibleDescription,
LegacyIAccessibleRole,
LegacyIAccessibleState,
LegacyIAccessibleHelp,
LegacyIAccessibleKeyboardShortcut,
LegacyIAccessibleSelection,
LegacyIAccessibleDefaultAction,
AriaRole,
AriaProperties,
IsDataValidForForm,
ControllerFor,
DescribedBy,
FlowsTo,
ProviderDescription,
IsItemContainerPatternAvailable,
IsVirtualizedItemPatternAvailable,
IsSynchronizedInputPatternAvailable,
OptimizeForVisualContent,
IsObjectModelPatternAvailable,
AnnotationAnnotationTypeId,
AnnotationAnnotationTypeName,
AnnotationAuthor,
AnnotationDateTime,
AnnotationTarget,
IsAnnotationPatternAvailable,
IsTextPattern2Available,
StylesStyleId,
StylesStyleName,
StylesFillColor,
StylesFillPatternStyle,
StylesShape,
StylesFillPatternColor,
StylesExtendedProperties,
IsStylesPatternAvailable,
IsSpreadsheetPatternAvailable,
SpreadsheetItemFormula,
SpreadsheetItemAnnotationObjects,
SpreadsheetItemAnnotationTypes,
IsSpreadsheetItemPatternAvailable,
Transform2CanZoom,
IsTransformPattern2Available,
LiveSetting,
IsTextChildPatternAvailable,
IsDragPatternAvailable,
DragIsGrabbed,
DragDropEffect,
DragDropEffects,
IsDropTargetPatternAvailable,
DropTargetDropTargetEffect,
DropTargetDropTargetEffects,
DragGrabbedItems,
Transform2ZoomLevel,
Transform2ZoomMinimum,
Transform2ZoomMaximum,
FlowsFrom,
IsTextEditPatternAvailable,
IsPeripheral,
IsCustomNavigationPatternAvailable,
PositionInSet,
SizeOfSet,
Level,
AnnotationTypes,
AnnotationObjects,
LandmarkType,
LocalizedLandmarkType,
FullDescription,
FillColor,
OutlineColor,
FillType,
VisualEffects,
OutlineThickness,
CenterPoint,
Rotatation,
Size
}
internal enum UiaPatternId
{
Invoke = 10000,
Selection,
Value,
RangeValue,
Scroll,
ExpandCollapse,
Grid,
GridItem,
MultipleView,
Window,
SelectionItem,
Dock,
Table,
TableItem,
Text,
Toggle,
Transform,
ScrollItem,
LegacyIAccessible,
ItemContainer,
VirtualizedItem,
SynchronizedInput,
ObjectModel,
Annotation,
Text2,
Styles,
Spreadsheet,
SpreadsheetItem,
Transform2,
TextChild,
Drag,
DropTarget,
TextEdit,
CustomNavigation
};
internal enum UiaControlTypeId
{
Button = 50000,
Calendar,
CheckBox,
ComboBox,
Edit,
Hyperlink,
Image,
ListItem,
List,
Menu,
MenuBar,
MenuItem,
ProgressBar,
RadioButton,
ScrollBar,
Slider,
Spinner,
StatusBar,
Tab,
TabItem,
Text,
ToolBar,
ToolTip,
Tree,
TreeItem,
Custom,
Group,
Thumb,
DataGrid,
DataItem,
Document,
SplitButton,
Window,
Pane,
Header,
HeaderItem,
Table,
TitleBar,
Separator,
SemanticZoom,
AppBar
};
[ComVisible(true)]
[Guid("d6dd68d1-86fd-4332-8666-9abedea2d24c")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IRawElementProviderSimple
{
ProviderOptions ProviderOptions { get; }
[return: MarshalAs(UnmanagedType.IUnknown)]
object? GetPatternProvider(int patternId);
object? GetPropertyValue(int propertyId);
IRawElementProviderSimple? HostRawElementProvider { get; }
}
}

14
src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderSimple2.cs

@ -0,0 +1,14 @@
using System;
using System.Runtime.InteropServices;
#nullable enable
namespace Avalonia.Win32.Interop.Automation
{
[ComVisible(true)]
[Guid("a0a839a9-8da1-4a82-806a-8e0d44e79f56")]
public interface IRawElementProviderSimple2
{
void ShowContextMenu();
}
}

13
src/Windows/Avalonia.Win32/Interop/Automation/IScrollItemProvider.cs

@ -0,0 +1,13 @@
using System;
using System.Runtime.InteropServices;
namespace Avalonia.Win32.Interop.Automation
{
[ComVisible(true)]
[Guid("2360c714-4bf1-4b26-ba65-9b21316127eb")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IScrollItemProvider
{
void ScrollIntoView();
}
}

21
src/Windows/Avalonia.Win32/Interop/Automation/IScrollProvider.cs

@ -0,0 +1,21 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.Automation.Provider;
namespace Avalonia.Win32.Interop.Automation
{
[ComVisible(true)]
[Guid("b38b8077-1fc3-42a5-8cae-d40c2215055a")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IScrollProvider
{
void Scroll(ScrollAmount horizontalAmount, ScrollAmount verticalAmount);
void SetScrollPercent(double horizontalPercent, double verticalPercent);
double HorizontalScrollPercent { get; }
double VerticalScrollPercent { get; }
double HorizontalViewSize { get; }
double VerticalViewSize { get; }
bool HorizontallyScrollable { [return: MarshalAs(UnmanagedType.Bool)] get; }
bool VerticallyScrollable { [return: MarshalAs(UnmanagedType.Bool)] get; }
}
}

17
src/Windows/Avalonia.Win32/Interop/Automation/ISelectionItemProvider.cs

@ -0,0 +1,17 @@
using System;
using System.Runtime.InteropServices;
namespace Avalonia.Win32.Interop.Automation
{
[ComVisible(true)]
[Guid("2acad808-b2d4-452d-a407-91ff1ad167b2")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ISelectionItemProvider
{
void Select();
void AddToSelection();
void RemoveFromSelection();
bool IsSelected { [return: MarshalAs(UnmanagedType.Bool)] get; }
IRawElementProviderSimple? SelectionContainer { get; }
}
}

15
src/Windows/Avalonia.Win32/Interop/Automation/ISelectionProvider.cs

@ -0,0 +1,15 @@
using System;
using System.Runtime.InteropServices;
namespace Avalonia.Win32.Interop.Automation
{
[ComVisible(true)]
[Guid("fb8b03af-3bdf-48d4-bd36-1a65793be168")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ISelectionProvider
{
IRawElementProviderSimple [] GetSelection();
bool CanSelectMultiple { [return: MarshalAs(UnmanagedType.Bool)] get; }
bool IsSelectionRequired { [return: MarshalAs(UnmanagedType.Bool)] get; }
}
}

26
src/Windows/Avalonia.Win32/Interop/Automation/ISynchronizedInputProvider.cs

@ -0,0 +1,26 @@
using System;
using System.Runtime.InteropServices;
namespace Avalonia.Win32.Interop.Automation
{
[ComVisible(true)]
[Guid("fdc8f176-aed2-477a-8c89-5604c66f278d")]
public enum SynchronizedInputType
{
KeyUp = 0x01,
KeyDown = 0x02,
MouseLeftButtonUp = 0x04,
MouseLeftButtonDown = 0x08,
MouseRightButtonUp = 0x10,
MouseRightButtonDown = 0x20
}
[ComVisible(true)]
[Guid("29db1a06-02ce-4cf7-9b42-565d4fab20ee")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ISynchronizedInputProvider
{
void StartListening(SynchronizedInputType inputType);
void Cancel();
}
}

14
src/Windows/Avalonia.Win32/Interop/Automation/ITableItemProvider.cs

@ -0,0 +1,14 @@
using System;
using System.Runtime.InteropServices;
namespace Avalonia.Win32.Interop.Automation
{
[ComVisible(true)]
[Guid("b9734fa6-771f-4d78-9c90-2517999349cd")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ITableItemProvider : IGridItemProvider
{
IRawElementProviderSimple [] GetRowHeaderItems();
IRawElementProviderSimple [] GetColumnHeaderItems();
}
}

24
src/Windows/Avalonia.Win32/Interop/Automation/ITableProvider.cs

@ -0,0 +1,24 @@
using System;
using System.Runtime.InteropServices;
namespace Avalonia.Win32.Interop.Automation
{
[ComVisible(true)]
[Guid("15fdf2e2-9847-41cd-95dd-510612a025ea")]
public enum RowOrColumnMajor
{
RowMajor,
ColumnMajor,
Indeterminate,
}
[ComVisible(true)]
[Guid("9c860395-97b3-490a-b52a-858cc22af166")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ITableProvider : IGridProvider
{
IRawElementProviderSimple [] GetRowHeaders();
IRawElementProviderSimple [] GetColumnHeaders();
RowOrColumnMajor RowOrColumnMajor { get; }
}
}

30
src/Windows/Avalonia.Win32/Interop/Automation/ITextProvider.cs

@ -0,0 +1,30 @@
using System;
using System.Runtime.InteropServices;
namespace Avalonia.Win32.Interop.Automation
{
[Flags]
[ComVisible(true)]
[Guid("3d9e3d8f-bfb0-484f-84ab-93ff4280cbc4")]
public enum SupportedTextSelection
{
None,
Single,
Multiple,
}
[ComVisible(true)]
[Guid("3589c92c-63f3-4367-99bb-ada653b77cf2")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ITextProvider
{
ITextRangeProvider [] GetSelection();
ITextRangeProvider [] GetVisibleRanges();
ITextRangeProvider RangeFromChild(IRawElementProviderSimple childElement);
ITextRangeProvider RangeFromPoint(Point screenLocation);
ITextRangeProvider DocumentRange { get; }
SupportedTextSelection SupportedTextSelection { get; }
}
}

48
src/Windows/Avalonia.Win32/Interop/Automation/ITextRangeProvider.cs

@ -0,0 +1,48 @@
using System.Runtime.InteropServices;
namespace Avalonia.Win32.Interop.Automation
{
public enum TextPatternRangeEndpoint
{
Start = 0,
End = 1,
}
public enum TextUnit
{
Character = 0,
Format = 1,
Word = 2,
Line = 3,
Paragraph = 4,
Page = 5,
Document = 6,
}
[ComVisible(true)]
[Guid("5347ad7b-c355-46f8-aff5-909033582f63")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ITextRangeProvider
{
ITextRangeProvider Clone();
[return: MarshalAs(UnmanagedType.Bool)]
bool Compare(ITextRangeProvider range);
int CompareEndpoints(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange, TextPatternRangeEndpoint targetEndpoint);
void ExpandToEnclosingUnit(TextUnit unit);
ITextRangeProvider FindAttribute(int attribute, object value, [MarshalAs(UnmanagedType.Bool)] bool backward);
ITextRangeProvider FindText(string text, [MarshalAs(UnmanagedType.Bool)] bool backward, [MarshalAs(UnmanagedType.Bool)] bool ignoreCase);
object GetAttributeValue(int attribute);
double [] GetBoundingRectangles();
IRawElementProviderSimple GetEnclosingElement();
string GetText(int maxLength);
int Move(TextUnit unit, int count);
int MoveEndpointByUnit(TextPatternRangeEndpoint endpoint, TextUnit unit, int count);
void MoveEndpointByRange(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange, TextPatternRangeEndpoint targetEndpoint);
void Select();
void AddToSelection();
void RemoveFromSelection();
void ScrollIntoView([MarshalAs(UnmanagedType.Bool)] bool alignToTop);
IRawElementProviderSimple[] GetChildren();
}
}

15
src/Windows/Avalonia.Win32/Interop/Automation/IToggleProvider.cs

@ -0,0 +1,15 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.Automation.Provider;
namespace Avalonia.Win32.Interop.Automation
{
[ComVisible(true)]
[Guid("56d00bd0-c4f4-433c-a836-1a52a57e0892")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IToggleProvider
{
void Toggle( );
ToggleState ToggleState { get; }
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save