Browse Source

Added HeadingLevel to AutomationProperties (#19696)

* Added HeadingLevel to AutomationProperties

* Added support for HeadlingLevel accessibility on Mac
release/11.3.7
Melissa 12 months ago
committed by Julien Lebosquain
parent
commit
3b11d97724
  1. 6
      native/Avalonia.Native/src/OSX/automation.mm
  2. 9
      samples/IntegrationTestApp/Pages/AutomationPage.axaml
  3. 6
      src/Avalonia.Controls/Automation/AutomationElementIdentifiers.cs
  4. 30
      src/Avalonia.Controls/Automation/AutomationProperties.cs
  5. 18
      src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs
  6. 1
      src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs
  7. 1
      src/Avalonia.Native/AvnAutomationPeer.cs
  8. 1
      src/Avalonia.Native/avn.idl
  9. 19
      src/Windows/Avalonia.Win32.Automation/AutomationNode.cs
  10. 23
      src/Windows/Avalonia.Win32.Automation/Interop/IRawElementProviderSimple.cs

6
native/Avalonia.Native/src/OSX/automation.mm

@ -122,7 +122,7 @@
case AutomationSplitButton: return NSAccessibilityPopUpButtonRole;
case AutomationWindow: return NSAccessibilityWindowRole;
case AutomationPane: return NSAccessibilityGroupRole;
case AutomationHeader: return NSAccessibilityGroupRole;
case AutomationHeader: return @"AXHeading";
case AutomationHeaderItem: return NSAccessibilityButtonRole;
case AutomationTable: return NSAccessibilityTableRole;
case AutomationTitleBar: return NSAccessibilityGroupRole;
@ -176,6 +176,10 @@
{
return GetNSStringAndRelease(_peer->GetName());
}
else if (_peer->GetAutomationControlType() == AutomationHeader)
{
return [NSNumber numberWithInt:_peer->GetHeadingLevel()];
}
return [super accessibilityValue];
}

9
samples/IntegrationTestApp/Pages/AutomationPage.axaml

@ -13,5 +13,14 @@
<TextBox Name="LabeledByTextBox" AutomationProperties.LabeledBy="{Binding #TextBlockAsLabel}">
Foo
</TextBox>
<TextBlock Name="TextBlockWithHeader1" AutomationProperties.ControlTypeOverride="Header" AutomationProperties.HeadingLevel="1">
Header 1
</TextBlock>
<TextBlock Name="TextBlockWithHeader2" AutomationProperties.ControlTypeOverride="Header" AutomationProperties.HeadingLevel="2">
Header 2
</TextBlock>
<TextBlock Name="TextBlockWithoutHeader">
Header None
</TextBlock>
</StackPanel>
</UserControl>

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

@ -30,5 +30,11 @@ namespace Avalonia.Automation
/// by the <see cref="AutomationPeer.GetHelpText"/> method.
/// </summary>
public static AutomationProperty HelpTextProperty { get; } = new AutomationProperty();
/// <summary>
/// Identifiers the heading level automation property. The class name property value is returned
/// by the <see cref="AutomationPeer.GetHeadingLevel"/> method.
/// </summary>
public static AutomationProperty HeadingLevelProperty { get; } = new AutomationProperty();
}
}

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

@ -104,6 +104,17 @@ namespace Avalonia.Automation
"HelpText",
typeof(AutomationProperties));
/// <summary>
/// Defines the AutomationProperties.HeadingLevel attached property.
/// </summary>
/// <remarks>
/// This property affects the default value for <see cref="AutomationPeer.GetHeadingLevel"/>.
/// </remarks>
public static readonly AttachedProperty<int> HeadingLevelProperty =
AvaloniaProperty.RegisterAttached<StyledElement, int>(
"HeadingLevel",
typeof(AutomationProperties));
/// <summary>
/// Defines the AutomationProperties.IsColumnHeader attached property.
/// </summary>
@ -348,6 +359,25 @@ namespace Avalonia.Automation
return element.GetValue(HelpTextProperty);
}
/// <summary>
/// Helper for setting the value of the <see cref="HeadingLevelProperty"/> on a StyledElement.
/// </summary>
public static void SetHeadingLevel(StyledElement element, int value)
{
_ = element ?? throw new ArgumentNullException(nameof(element));
element.SetValue(HeadingLevelProperty, value);
}
/// <summary>
/// Helper for reading the value of the <see cref="HeadingLevelProperty"/> on a StyledElement.
/// </summary>
/// <returns></returns>
public static int GetHeadingLevel(StyledElement element)
{
_ = element ?? throw new ArgumentNullException(nameof(element));
return element.GetValue(HeadingLevelProperty);
}
/// <summary>
/// Helper for setting the value of the <see cref="IsColumnHeaderProperty"/> on a StyledElement.
/// </summary>

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

@ -270,6 +270,23 @@ namespace Avalonia.Automation.Peers
/// </remarks>
public string GetHelpText() => GetHelpTextCore() ?? string.Empty;
/// <summary>
/// Gets the heading level that is associated with this automation peer.
/// </summary>
/// <remarks>
/// <list type="table">
/// <item>
/// <term>Windows</term>
/// <description><c>UIA_HeadingLevelPropertyId</c></description>
/// </item>
/// <item>
/// <term>macOS</term>
/// <description><c>NSAccessibilityProtocol.accessibilityValue</c></description>
/// </item>
/// </list>
/// </remarks>
public int GetHeadingLevel() => GetHeadingLevelCore();
/// <summary>
/// Gets the <see cref="AutomationPeer"/> that is the parent of this <see cref="AutomationPeer"/>.
/// </summary>
@ -503,6 +520,7 @@ namespace Avalonia.Automation.Peers
protected abstract AutomationPeer? GetLabeledByCore();
protected abstract string? GetNameCore();
protected virtual string? GetHelpTextCore() => null;
protected virtual int GetHeadingLevelCore() => 0;
protected abstract AutomationPeer? GetParentCore();
protected abstract bool HasKeyboardFocusCore();
protected abstract bool IsContentElementCore();

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

@ -127,6 +127,7 @@ namespace Avalonia.Automation.Peers
return result;
}
protected override int GetHeadingLevelCore() => AutomationProperties.GetHeadingLevel(Owner);
protected override AutomationPeer? GetParentCore()
{
EnsureConnected();

1
src/Avalonia.Native/AvnAutomationPeer.cs

@ -40,6 +40,7 @@ namespace Avalonia.Native
public IAvnAutomationPeer? LabeledBy => Wrap(_inner.GetLabeledBy());
public IAvnString Name => _inner.GetName().ToAvnString();
public IAvnString HelpText => _inner.GetHelpText().ToAvnString();
public int HeadingLevel => _inner.GetHeadingLevel();
public IAvnAutomationPeer? Parent => Wrap(_inner.GetParent());
public IAvnAutomationPeer? VisualRoot => Wrap(_inner.GetVisualRoot());

1
src/Avalonia.Native/avn.idl

@ -1238,6 +1238,7 @@ interface IAvnAutomationPeer : IUnknown
void ValueProvider_SetValue(char* value);
IAvnString* GetHelpText();
int GetHeadingLevel();
}
[uuid(b00af5da-78af-4b33-bfff-4ce13a6239a9)]

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

@ -37,6 +37,7 @@ namespace Avalonia.Win32.Automation
{ AutomationElementIdentifiers.ClassNameProperty, UiaPropertyId.ClassName },
{ AutomationElementIdentifiers.NameProperty, UiaPropertyId.Name },
{ AutomationElementIdentifiers.HelpTextProperty, UiaPropertyId.HelpText },
{ AutomationElementIdentifiers.HeadingLevelProperty, UiaPropertyId.HeadingLevel },
{ ExpandCollapsePatternIdentifiers.ExpandCollapseStateProperty, UiaPropertyId.ExpandCollapseExpandCollapseState },
{ RangeValuePatternIdentifiers.IsReadOnlyProperty, UiaPropertyId.RangeValueIsReadOnly},
{ RangeValuePatternIdentifiers.MaximumProperty, UiaPropertyId.RangeValueMaximum },
@ -138,6 +139,7 @@ namespace Avalonia.Win32.Automation
UiaPropertyId.LocalizedControlType => InvokeSync(() => Peer.GetLocalizedControlType()),
UiaPropertyId.Name => InvokeSync(() => Peer.GetName()),
UiaPropertyId.HelpText => InvokeSync(() => Peer.GetHelpText()),
UiaPropertyId.HeadingLevel => InvokeSync(() => ToUiaHeadingLevel(Peer.GetHeadingLevel())),
UiaPropertyId.ProcessId => s_pid,
UiaPropertyId.RuntimeId => _runtimeId,
_ => null,
@ -358,6 +360,23 @@ namespace Avalonia.Win32.Automation
};
}
private static UiaHeadingLevel ToUiaHeadingLevel(int level)
{
return level switch
{
1 => UiaHeadingLevel.Level1,
2 => UiaHeadingLevel.Level2,
3 => UiaHeadingLevel.Level3,
4 => UiaHeadingLevel.Level4,
5 => UiaHeadingLevel.Level5,
6 => UiaHeadingLevel.Level6,
7 => UiaHeadingLevel.Level7,
8 => UiaHeadingLevel.Level8,
9 => UiaHeadingLevel.Level9,
_ => UiaHeadingLevel.None,
};
}
private static int GetProcessId()
{
#if NET6_0_OR_GREATER

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

@ -184,7 +184,14 @@ internal enum UiaPropertyId
OutlineThickness,
CenterPoint,
Rotatation,
Size
Size,
IsSelectionPattern2Available,
Selection2FirstSelectedItem,
Selection2LastSelectedItem,
Selection2CurrentSelectedItem,
Selection2ItemCount,
HeadingLevel,
IsDialog
}
internal enum UiaPatternId
@ -270,6 +277,20 @@ internal enum UiaControlTypeId
AppBar
};
internal enum UiaHeadingLevel
{
None = 80050,
Level1,
Level2,
Level3,
Level4,
Level5,
Level6,
Level7,
Level8,
Level9
};
#if NET8_0_OR_GREATER
[GeneratedComInterface]
#else

Loading…
Cancel
Save