Adding UIA provider support to RadioButtonAccessibleObject (#3244)

This commit is contained in:
Mikhail Lipin 2020-06-30 14:39:55 +03:00 коммит произвёл GitHub
Родитель fdb4350682
Коммит eff8a5d553
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
5 изменённых файлов: 334 добавлений и 62 удалений

Просмотреть файл

@ -10484,7 +10484,7 @@ System.Windows.Forms.InputLanguageCollection.this[int index].get -> System.Windo
~System.Windows.Forms.QueryAccessibilityHelpEventArgs.HelpString.get -> string
~System.Windows.Forms.QueryAccessibilityHelpEventArgs.HelpString.set -> void
~System.Windows.Forms.QueryAccessibilityHelpEventArgs.QueryAccessibilityHelpEventArgs(string helpNamespace, string helpString, string helpKeyword) -> void
~System.Windows.Forms.RadioButton.RadioButtonAccessibleObject.RadioButtonAccessibleObject(System.Windows.Forms.RadioButton owner) -> void
System.Windows.Forms.RadioButton.RadioButtonAccessibleObject.RadioButtonAccessibleObject(System.Windows.Forms.RadioButton! owner) -> void
~System.Windows.Forms.RelatedImageListAttribute.RelatedImageList.get -> string
~System.Windows.Forms.RelatedImageListAttribute.RelatedImageListAttribute(string relatedImageList) -> void
~System.Windows.Forms.RetrieveVirtualItemEventArgs.Item.get -> System.Windows.Forms.ListViewItem
@ -11768,7 +11768,7 @@ override System.Windows.Forms.InputLanguage.Equals(object? value) -> bool
~override System.Windows.Forms.RadioButton.OnEnter(System.EventArgs e) -> void
~override System.Windows.Forms.RadioButton.OnHandleCreated(System.EventArgs e) -> void
~override System.Windows.Forms.RadioButton.OnMouseUp(System.Windows.Forms.MouseEventArgs mevent) -> void
~override System.Windows.Forms.RadioButton.RadioButtonAccessibleObject.DefaultAction.get -> string
override System.Windows.Forms.RadioButton.RadioButtonAccessibleObject.DefaultAction.get -> string!
~override System.Windows.Forms.RadioButton.ToString() -> string
~override System.Windows.Forms.RichTextBox.BackgroundImage.get -> System.Drawing.Image
~override System.Windows.Forms.RichTextBox.BackgroundImage.set -> void

Просмотреть файл

@ -0,0 +1,79 @@
// 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.
using static Interop;
namespace System.Windows.Forms
{
public partial class RadioButton
{
public class RadioButtonAccessibleObject : ControlAccessibleObject
{
private readonly RadioButton _owningRadioButton;
public RadioButtonAccessibleObject(RadioButton owner) : base(owner)
{
_owningRadioButton = owner;
}
public override string DefaultAction
=> Owner.AccessibleDefaultActionDescription ?? SR.AccessibleActionCheck;
public override AccessibleRole Role
{
get
{
AccessibleRole role = Owner.AccessibleRole;
return role != AccessibleRole.Default
? role
: AccessibleRole.RadioButton;
}
}
public override AccessibleStates State
=> _owningRadioButton.Checked
? AccessibleStates.Checked | base.State
: base.State;
internal override bool IsItemSelected
=> _owningRadioButton.Checked;
public override void DoDefaultAction()
{
if (_owningRadioButton.IsHandleCreated)
{
_owningRadioButton.PerformClick();
}
}
internal override object? GetPropertyValue(UiaCore.UIA propertyID)
=> propertyID switch
{
UiaCore.UIA.NamePropertyId
=> Name,
UiaCore.UIA.AutomationIdPropertyId
=> Owner.Name,
UiaCore.UIA.IsSelectionItemPatternAvailablePropertyId
=> IsPatternSupported(UiaCore.UIA.SelectionItemPatternId),
UiaCore.UIA.ControlTypePropertyId
=> UiaCore.UIA.RadioButtonControlTypeId,
UiaCore.UIA.IsKeyboardFocusablePropertyId
// This is necessary for compatibility with MSAA proxy:
// IsKeyboardFocusable = true regardless the control is enabled/disabled.
=> true,
UiaCore.UIA.HasKeyboardFocusPropertyId
=> Owner.Focused,
_ => base.GetPropertyValue(propertyID)
};
internal override bool IsPatternSupported(UiaCore.UIA patternId)
=> patternId switch
{
var p when
p == UiaCore.UIA.SelectionItemPatternId => true,
_ => base.IsPatternSupported(patternId)
};
}
}
}

Просмотреть файл

@ -24,7 +24,7 @@ namespace System.Windows.Forms
[ToolboxItem("System.Windows.Forms.Design.AutoSizeToolboxItem," + AssemblyRef.SystemDesign)]
[Designer("System.Windows.Forms.Design.RadioButtonDesigner, " + AssemblyRef.SystemDesign)]
[SRDescription(nameof(SR.DescriptionRadioButton))]
public class RadioButton : ButtonBase
public partial class RadioButton : ButtonBase
{
private static readonly object EVENT_CHECKEDCHANGED = new object();
private static readonly ContentAlignment anyRight = ContentAlignment.TopRight | ContentAlignment.MiddleRight | ContentAlignment.BottomRight;
@ -344,6 +344,8 @@ namespace System.Windows.Forms
}
}
internal override bool SupportsUiaProviders => true;
[DefaultValue(false)]
new public bool TabStop
{
@ -400,8 +402,14 @@ namespace System.Windows.Forms
/// </summary>
protected virtual void OnCheckedChanged(EventArgs e)
{
// MSAA events:
AccessibilityNotifyClients(AccessibleEvents.StateChange, -1);
AccessibilityNotifyClients(AccessibleEvents.NameChange, -1);
// UIA events:
AccessibilityObject.RaiseAutomationPropertyChangedEvent(UiaCore.UIA.SelectionItemIsSelectedPropertyId, Checked, !Checked);
AccessibilityObject.RaiseAutomationEvent(UiaCore.UIA.AutomationPropertyChangedEventId);
((EventHandler)Events[EVENT_CHECKEDCHANGED])?.Invoke(this, e);
}
@ -600,56 +608,5 @@ namespace System.Windows.Forms
string s = base.ToString();
return s + ", Checked: " + Checked.ToString();
}
public class RadioButtonAccessibleObject : ButtonBaseAccessibleObject
{
public RadioButtonAccessibleObject(RadioButton owner) : base(owner)
{
}
public override string DefaultAction
{
get
{
string defaultAction = Owner.AccessibleDefaultActionDescription;
if (defaultAction != null)
{
return defaultAction;
}
return SR.AccessibleActionCheck;
}
}
public override AccessibleRole Role
{
get
{
AccessibleRole role = Owner.AccessibleRole;
if (role != AccessibleRole.Default)
{
return role;
}
return AccessibleRole.RadioButton;
}
}
public override AccessibleStates State
{
get
{
if (((RadioButton)Owner).Checked)
{
return AccessibleStates.Checked | base.State;
}
return base.State;
}
}
public override void DoDefaultAction()
{
((RadioButton)Owner).PerformClick();
}
}
}
}

Просмотреть файл

@ -0,0 +1,176 @@
// 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.
using Xunit;
using static Interop.UiaCore;
namespace System.Windows.Forms.Tests
{
public class RadioButton_RadioButtonAccessibleObjectTests
{
[WinFormsFact]
public void RadioButtonAccessibleObject_Ctor_NullControl_ThrowsArgumentNullException()
{
Assert.Throws<ArgumentNullException>(() => new RadioButton.RadioButtonAccessibleObject(null));
}
[WinFormsFact]
public void RadioButtonAccessibleObject_Ctor_Default()
{
using var radioButton = new RadioButton();
Assert.False(radioButton.IsHandleCreated);
var radioButtonAccessibleObject = new RadioButton.RadioButtonAccessibleObject(radioButton);
Assert.NotNull(radioButtonAccessibleObject.Owner);
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.True(radioButton.IsHandleCreated);
}
[WinFormsFact]
public void RadioButtonAccessibleObject_DefaultAction_ReturnsExpected()
{
using var radioButton = new RadioButton
{
AccessibleDefaultActionDescription = "TestActionDescription"
};
Assert.False(radioButton.IsHandleCreated);
var radioButtonAccessibleObject = new RadioButton.RadioButtonAccessibleObject(radioButton);
Assert.Equal("TestActionDescription", radioButtonAccessibleObject.DefaultAction);
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.True(radioButton.IsHandleCreated);
}
[WinFormsFact]
public void RadioButtonAccessibleObject_Description_ReturnsExpected()
{
using var radioButton = new RadioButton
{
AccessibleDescription = "TestDescription"
};
Assert.False(radioButton.IsHandleCreated);
var radioButtonAccessibleObject = new RadioButton.RadioButtonAccessibleObject(radioButton);
Assert.Equal("TestDescription", radioButtonAccessibleObject.Description);
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.True(radioButton.IsHandleCreated);
}
[WinFormsFact]
public void RadioButtonAccessibleObject_Name_ReturnsExpected()
{
using var radioButton = new RadioButton
{
AccessibleName = "TestName"
};
Assert.False(radioButton.IsHandleCreated);
var radioButtonAccessibleObject = new RadioButton.RadioButtonAccessibleObject(radioButton);
Assert.Equal("TestName", radioButtonAccessibleObject.Name);
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.True(radioButton.IsHandleCreated);
}
[WinFormsFact]
public void RadioButtonAccessibleObject_Role_Custom_ReturnsExpected()
{
using var radioButton = new RadioButton
{
AccessibleRole = AccessibleRole.PushButton
};
Assert.False(radioButton.IsHandleCreated);
var radioButtonAccessibleObject = new RadioButton.RadioButtonAccessibleObject(radioButton);
Assert.Equal(AccessibleRole.PushButton, radioButtonAccessibleObject.Role);
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.True(radioButton.IsHandleCreated);
}
[WinFormsFact]
public void RadioButtonAccessibleObject_Role_Default_ReturnsExpected()
{
using var radioButton = new RadioButton();
Assert.False(radioButton.IsHandleCreated);
var radioButtonAccessibleObject = new RadioButton.RadioButtonAccessibleObject(radioButton);
Assert.Equal(AccessibleRole.RadioButton, radioButtonAccessibleObject.Role);
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.True(radioButton.IsHandleCreated);
}
[WinFormsFact]
public void RadioButtonAccessibleObject_State_ReturnsExpected()
{
using var radioButton = new RadioButton();
Assert.False(radioButton.IsHandleCreated);
var radioButtonAccessibleObject = new RadioButton.RadioButtonAccessibleObject(radioButton);
Assert.Equal(AccessibleStates.Focusable, radioButtonAccessibleObject.State);
radioButtonAccessibleObject.DoDefaultAction();
Assert.Equal(AccessibleStates.Focusable | AccessibleStates.Checked, radioButtonAccessibleObject.State);
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.True(radioButton.IsHandleCreated);
}
[WinFormsFact]
public void RadioButtonAccessibleObject_IsItemSelected_ReturnsExpected()
{
using var radioButton = new RadioButton();
Assert.False(radioButton.IsHandleCreated);
var radioButtonAccessibleObject = new RadioButton.RadioButtonAccessibleObject(radioButton);
Assert.False(radioButtonAccessibleObject.IsItemSelected);
radioButtonAccessibleObject.DoDefaultAction();
Assert.True(radioButtonAccessibleObject.IsItemSelected);
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.True(radioButton.IsHandleCreated);
}
[WinFormsTheory]
[InlineData((int)UIA.NamePropertyId, "TestName")]
[InlineData((int)UIA.ControlTypePropertyId, UIA.RadioButtonControlTypeId)]
[InlineData((int)UIA.IsKeyboardFocusablePropertyId, true)]
[InlineData((int)UIA.AutomationIdPropertyId, "RadioButton1")]
public void RadioButtonAccessibleObject_GetPropertyValue_Invoke_ReturnsExpected(int propertyID, object expected)
{
using var radioButton = new RadioButton
{
Name = "RadioButton1",
AccessibleName = "TestName"
};
Assert.False(radioButton.IsHandleCreated);
var radioButtonAccessibleObject = new RadioButton.RadioButtonAccessibleObject(radioButton);
object value = radioButtonAccessibleObject.GetPropertyValue((UIA)propertyID);
Assert.Equal(expected, value);
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.True(radioButton.IsHandleCreated);
}
[WinFormsTheory]
[InlineData((int)UIA.LegacyIAccessiblePatternId)]
[InlineData((int)UIA.SelectionItemPatternId)]
public void RadioButtonAccessibleObject_IsPatternSupported_Invoke_ReturnsExpected(int patternId)
{
using var radioButton = new RadioButton
{
Name = "RadioButton1"
};
Assert.False(radioButton.IsHandleCreated);
var radioButtonAccessibleObject = new RadioButton.RadioButtonAccessibleObject(radioButton);
Assert.True(radioButtonAccessibleObject.IsPatternSupported((UIA)patternId));
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.True(radioButton.IsHandleCreated);
}
}
}

Просмотреть файл

@ -11,6 +11,7 @@ using Moq;
using WinForms.Common.Tests;
using Xunit;
using static Interop;
using static Interop.UiaCore;
using static Interop.User32;
namespace System.Windows.Forms.Tests
@ -529,13 +530,15 @@ namespace System.Windows.Forms.Tests
control.CheckedChanged += handler;
control.OnCheckedChanged(eventArgs);
Assert.Equal(1, callCount);
Assert.False(control.IsHandleCreated);
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.True(control.IsHandleCreated);
// Remove handler.
control.CheckedChanged -= handler;
control.OnCheckedChanged(eventArgs);
Assert.Equal(1, callCount);
Assert.False(control.IsHandleCreated);
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.True(control.IsHandleCreated);
}
[WinFormsTheory]
@ -610,14 +613,16 @@ namespace System.Windows.Forms.Tests
control.OnClick(eventArgs);
Assert.Equal(1, callCount);
Assert.Equal(autoCheck, control.Checked);
Assert.False(control.IsHandleCreated);
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.Equal(autoCheck, control.IsHandleCreated);
// Remove handler.
control.Click -= handler;
control.OnClick(eventArgs);
Assert.Equal(1, callCount);
Assert.Equal(autoCheck, control.Checked);
Assert.False(control.IsHandleCreated);
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.Equal(autoCheck, control.IsHandleCreated);
}
public static IEnumerable<object[]> OnClick_WithHandle_TestData()
@ -716,13 +721,15 @@ namespace System.Windows.Forms.Tests
control.Enter += handler;
control.OnEnter(eventArgs);
Assert.Equal(1, callCount);
Assert.False(control.IsHandleCreated);
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.True(control.IsHandleCreated);
// Remove handler.
control.Enter -= handler;
control.OnEnter(eventArgs);
Assert.Equal(1, callCount);
Assert.False(control.IsHandleCreated);
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.True(control.IsHandleCreated);
}
[WinFormsTheory]
@ -1143,13 +1150,15 @@ namespace System.Windows.Forms.Tests
control.Click += handler;
control.PerformClick();
Assert.Equal(1, callCount);
Assert.False(control.IsHandleCreated);
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.True(control.IsHandleCreated);
// Remove handler.
control.Click -= handler;
control.PerformClick();
Assert.Equal(1, callCount);
Assert.False(control.IsHandleCreated);
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.True(control.IsHandleCreated);
}
[WinFormsFact]
@ -1330,6 +1339,24 @@ namespace System.Windows.Forms.Tests
Assert.Equal(0, createdCallCount);
}
[WinFormsFact]
public void RadioButton_RaiseAutomationEvent_Invoke_Success()
{
using var radioButton = new TestRadioButton();
Assert.False(radioButton.IsHandleCreated);
var accessibleObject = (SubRadioButtonAccessibleObject)radioButton.AccessibilityObject;
Assert.Equal(0, accessibleObject.RaiseAutomationEventCallsCount);
Assert.Equal(0, accessibleObject.RaiseAutomationPropertyChangedEventCallsCount);
radioButton.PerformClick();
Assert.Equal(1, accessibleObject.RaiseAutomationEventCallsCount);
Assert.Equal(1, accessibleObject.RaiseAutomationPropertyChangedEventCallsCount);
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.True(radioButton.IsHandleCreated);
}
[WinFormsTheory]
[InlineData(1, 2)]
[InlineData(0, 0)]
@ -1454,5 +1481,38 @@ namespace System.Windows.Forms.Tests
public new void SetStyle(ControlStyles flag, bool value) => base.SetStyle(flag, value);
}
private class TestRadioButton : RadioButton
{
protected override AccessibleObject CreateAccessibilityInstance()
{
return new SubRadioButtonAccessibleObject(this);
}
}
private class SubRadioButtonAccessibleObject : RadioButton.RadioButtonAccessibleObject
{
public SubRadioButtonAccessibleObject(RadioButton owner) : base(owner)
{
RaiseAutomationEventCallsCount = 0;
RaiseAutomationPropertyChangedEventCallsCount = 0;
}
public int RaiseAutomationEventCallsCount { get; private set; }
public int RaiseAutomationPropertyChangedEventCallsCount { get; private set; }
internal override bool RaiseAutomationEvent(UIA eventId)
{
RaiseAutomationEventCallsCount++;
return base.RaiseAutomationEvent(eventId);
}
internal override bool RaiseAutomationPropertyChangedEvent(UIA propertyId, object oldValue, object newValue)
{
RaiseAutomationPropertyChangedEventCallsCount++;
return base.RaiseAutomationPropertyChangedEvent(propertyId, oldValue, newValue);
}
}
}
}