Adding CheckBox control UIA accessibility (#3228)

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

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

@ -9335,7 +9335,7 @@ System.Windows.Forms.ButtonBase.ButtonBaseAccessibleObject.ButtonBaseAccessibleO
~System.Windows.Forms.ButtonBase.ImageKey.set -> void
~System.Windows.Forms.ButtonBase.ImageList.get -> System.Windows.Forms.ImageList
~System.Windows.Forms.ButtonBase.ImageList.set -> void
~System.Windows.Forms.CheckBox.CheckBoxAccessibleObject.CheckBoxAccessibleObject(System.Windows.Forms.Control owner) -> void
System.Windows.Forms.CheckBox.CheckBoxAccessibleObject.CheckBoxAccessibleObject(System.Windows.Forms.Control! owner) -> void
~System.Windows.Forms.CheckedListBox.CheckedIndexCollection.CopyTo(System.Array dest, int index) -> void
~System.Windows.Forms.CheckedListBox.CheckedIndexCollection.GetEnumerator() -> System.Collections.IEnumerator
~System.Windows.Forms.CheckedListBox.CheckedIndices.get -> System.Windows.Forms.CheckedListBox.CheckedIndexCollection
@ -11066,7 +11066,7 @@ System.Windows.Forms.VScrollProperties.VScrollProperties(System.Windows.Forms.Sc
~override System.Windows.Forms.ButtonBase.OnVisibleChanged(System.EventArgs e) -> void
~override System.Windows.Forms.ButtonBase.Text.get -> string
~override System.Windows.Forms.ButtonBase.Text.set -> void
~override System.Windows.Forms.CheckBox.CheckBoxAccessibleObject.DefaultAction.get -> string
override System.Windows.Forms.CheckBox.CheckBoxAccessibleObject.DefaultAction.get -> string!
~override System.Windows.Forms.CheckBox.CreateAccessibilityInstance() -> System.Windows.Forms.AccessibleObject
~override System.Windows.Forms.CheckBox.CreateParams.get -> System.Windows.Forms.CreateParams
~override System.Windows.Forms.CheckBox.OnClick(System.EventArgs e) -> void

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

@ -0,0 +1,118 @@
// 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 System.Runtime.InteropServices;
using static Interop;
namespace System.Windows.Forms
{
public partial class CheckBox
{
public class CheckBoxAccessibleObject : ButtonBaseAccessibleObject
{
private readonly CheckBox _owningCheckBox;
public CheckBoxAccessibleObject(Control owner)
: base((owner is CheckBox owningCheckBox) ? owner : throw new ArgumentException(string.Format(SR.ConstructorArgumentInvalidValueType, nameof(Owner), typeof(CheckBox))))
{
_owningCheckBox = owningCheckBox;
}
public override string DefaultAction
{
get
{
string defaultAction = Owner.AccessibleDefaultActionDescription;
if (defaultAction != null)
{
return defaultAction;
}
return _owningCheckBox.Checked
? SR.AccessibleActionUncheck
: SR.AccessibleActionCheck;
}
}
public override AccessibleRole Role
{
get
{
AccessibleRole role = Owner.AccessibleRole;
return role != AccessibleRole.Default
? role
: AccessibleRole.CheckButton;
}
}
public override AccessibleStates State
=> _owningCheckBox.CheckState switch
{
CheckState.Checked => AccessibleStates.Checked | base.State,
CheckState.Indeterminate => AccessibleStates.Indeterminate | base.State,
_ => base.State
};
internal override UiaCore.ToggleState ToggleState
=> _owningCheckBox.Checked
? UiaCore.ToggleState.On
: UiaCore.ToggleState.Off;
internal override bool IsPatternSupported(UiaCore.UIA patternId)
=> patternId switch
{
var p when
p == UiaCore.UIA.TogglePatternId => true,
_ => base.IsPatternSupported(patternId)
};
internal override object? GetPropertyValue(UiaCore.UIA propertyID)
=> propertyID switch
{
UiaCore.UIA.NamePropertyId
=> Name,
UiaCore.UIA.AutomationIdPropertyId
=> Owner.Name,
UiaCore.UIA.IsTogglePatternAvailablePropertyId
=> IsPatternSupported(UiaCore.UIA.TogglePatternId),
UiaCore.UIA.ControlTypePropertyId
=> UiaCore.UIA.CheckBoxControlTypeId,
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)
};
public override void DoDefaultAction()
{
if (_owningCheckBox.IsHandleCreated)
{
_owningCheckBox.AccObjDoDefaultAction = true;
}
try
{
base.DoDefaultAction();
}
finally
{
if (_owningCheckBox.IsHandleCreated)
{
_owningCheckBox.AccObjDoDefaultAction = false;
}
}
}
internal override void Toggle()
{
_owningCheckBox.Checked = !_owningCheckBox.Checked;
}
}
}
}

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

@ -23,7 +23,7 @@ namespace System.Windows.Forms
[DefaultBindingProperty(nameof(CheckState))]
[ToolboxItem("System.Windows.Forms.Design.AutoSizeToolboxItem," + AssemblyRef.SystemDesign)]
[SRDescription(nameof(SR.DescriptionCheckBox))]
public class CheckBox : ButtonBase
public partial class CheckBox : ButtonBase
{
private static readonly object EVENT_CHECKEDCHANGED = new object();
private static readonly object EVENT_CHECKSTATECHANGED = new object();
@ -405,6 +405,8 @@ namespace System.Windows.Forms
}
}
internal override bool SupportsUiaProviders => true;
/// <summary>
/// Gets or sets a value indicating the alignment of the
/// text on the checkbox control.
@ -489,9 +491,14 @@ namespace System.Windows.Forms
AccessibilityNotifyClients(AccessibleEvents.SystemCaptureStart, -1);
}
// MSAA events:
AccessibilityNotifyClients(AccessibleEvents.StateChange, -1);
AccessibilityNotifyClients(AccessibleEvents.NameChange, -1);
// UIA events:
AccessibilityObject.RaiseAutomationPropertyChangedEvent(UiaCore.UIA.NamePropertyId, Name, Name);
AccessibilityObject.RaiseAutomationEvent(UiaCore.UIA.AutomationPropertyChangedEventId);
if (FlatStyle == FlatStyle.System)
{
AccessibilityNotifyClients(AccessibleEvents.SystemCaptureEnd, -1);
@ -645,84 +652,5 @@ namespace System.Windows.Forms
int checkState = (int)CheckState;
return s + ", CheckState: " + checkState.ToString(CultureInfo.InvariantCulture);
}
public class CheckBoxAccessibleObject : ButtonBaseAccessibleObject
{
public CheckBoxAccessibleObject(Control owner) : base(owner)
{
}
public override string DefaultAction
{
get
{
string defaultAction = Owner.AccessibleDefaultActionDescription;
if (defaultAction != null)
{
return defaultAction;
}
if (((CheckBox)Owner).Checked)
{
return SR.AccessibleActionUncheck;
}
else
{
return SR.AccessibleActionCheck;
}
}
}
public override AccessibleRole Role
{
get
{
AccessibleRole role = Owner.AccessibleRole;
if (role != AccessibleRole.Default)
{
return role;
}
return AccessibleRole.CheckButton;
}
}
public override AccessibleStates State
{
get
{
switch (((CheckBox)Owner).CheckState)
{
case CheckState.Checked:
return AccessibleStates.Checked | base.State;
case CheckState.Indeterminate:
return AccessibleStates.Indeterminate | base.State;
}
return base.State;
}
}
public override void DoDefaultAction()
{
CheckBox cb = Owner as CheckBox;
if (cb != null)
{
cb.AccObjDoDefaultAction = true;
}
try
{
base.DoDefaultAction();
}
finally
{
if (cb != null)
{
cb.AccObjDoDefaultAction = false;
}
}
}
}
}
}

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

@ -0,0 +1,195 @@
// 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 CheckBox_CheckBoxAccessibleObjectTests
{
[WinFormsFact]
public void CheckBoxAccessibleObject_Ctor_NullControl_ThrowsArgumentException()
{
Assert.Throws<ArgumentException>(() => new CheckBox.CheckBoxAccessibleObject(null));
}
[WinFormsFact]
public void CheckBoxAccessibleObject_Ctor_InvalidTypeControl_ThrowsArgumentException()
{
using var textBox = new TextBox();
Assert.Throws<ArgumentException>(() => new CheckBox.CheckBoxAccessibleObject(textBox));
}
[WinFormsFact]
public void CheckBoxAccessibleObject_Ctor_Default()
{
using var checkBox = new CheckBox();
Assert.False(checkBox.IsHandleCreated);
var checkBoxAccessibleObject = new CheckBox.CheckBoxAccessibleObject(checkBox);
Assert.NotNull(checkBoxAccessibleObject.Owner);
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.True(checkBox.IsHandleCreated);
}
[WinFormsFact]
public void CheckBoxAccessibleObject_CustomDoDefaultAction_ReturnsExpected()
{;
using var checkBox = new CheckBox
{
Name = "CheckBox1",
AccessibleDefaultActionDescription = "TestActionDescription"
};
Assert.False(checkBox.IsHandleCreated);
var checkBoxAccessibleObject = new CheckBox.CheckBoxAccessibleObject(checkBox);
Assert.Equal("TestActionDescription", checkBoxAccessibleObject.DefaultAction);
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.True(checkBox.IsHandleCreated);
}
[WinFormsFact]
public void CheckBoxAccessibleObject_DefaultRole_ReturnsExpected()
{
using var checkBox = new CheckBox();
Assert.False(checkBox.IsHandleCreated);
var checkBoxAccessibleObject = new CheckBox.CheckBoxAccessibleObject(checkBox);
Assert.Equal(AccessibleRole.CheckButton, checkBoxAccessibleObject.Role);
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.True(checkBox.IsHandleCreated);
}
[WinFormsFact]
public void CheckBoxAccessibleObject_CustomRole_ReturnsExpected()
{
using var checkBox = new CheckBox
{
AccessibleRole = AccessibleRole.PushButton
};
Assert.False(checkBox.IsHandleCreated);
var checkBoxAccessibleObject = new CheckBox.CheckBoxAccessibleObject(checkBox);
Assert.Equal(AccessibleRole.PushButton, checkBoxAccessibleObject.Role);
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.True(checkBox.IsHandleCreated);
}
[WinFormsFact]
public void CheckBoxAccessibleObject_State_ReturnsExpected()
{
using var checkBox = new CheckBox();
Assert.False(checkBox.IsHandleCreated);
var checkBoxAccessibleObject = new CheckBox.CheckBoxAccessibleObject(checkBox);
Assert.Equal(AccessibleStates.Focusable, checkBoxAccessibleObject.State);
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.True(checkBox.IsHandleCreated);
}
[WinFormsFact]
public void CheckBoxAccessibleObject_ToggleState_ReturnsExpected()
{
using var checkBox = new CheckBox();
Assert.False(checkBox.IsHandleCreated);
var checkBoxAccessibleObject = new CheckBox.CheckBoxAccessibleObject(checkBox);
Assert.Equal(ToggleState.Off, checkBoxAccessibleObject.ToggleState);
checkBoxAccessibleObject.DoDefaultAction();
Assert.Equal(ToggleState.On, checkBoxAccessibleObject.ToggleState);
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.True(checkBox.IsHandleCreated);
}
[WinFormsFact]
public void CheckBoxAccessibleObject_Description_ReturnsExpected()
{
using var checkBox = new CheckBox
{
AccessibleDescription = "TestDescription"
};
Assert.False(checkBox.IsHandleCreated);
var checkBoxAccessibleObject = new CheckBox.CheckBoxAccessibleObject(checkBox);
Assert.Equal("TestDescription", checkBoxAccessibleObject.Description);
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.True(checkBox.IsHandleCreated);
}
[WinFormsFact]
public void CheckBoxAccessibleObject_Name_ReturnsExpected()
{
using var checkBox = new CheckBox
{
AccessibleName = "TestName"
};
Assert.False(checkBox.IsHandleCreated);
var checkBoxAccessibleObject = new CheckBox.CheckBoxAccessibleObject(checkBox);
Assert.Equal("TestName", checkBoxAccessibleObject.Name);
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.True(checkBox.IsHandleCreated);
}
[WinFormsTheory]
[InlineData((int)UIA.NamePropertyId, "TestName")]
[InlineData((int)UIA.ControlTypePropertyId, UIA.CheckBoxControlTypeId)]
[InlineData((int)UIA.IsKeyboardFocusablePropertyId, true)]
[InlineData((int)UIA.AutomationIdPropertyId, "CheckBox1")]
public void CheckBoxAccessibleObject_GetPropertyValue_Invoke_ReturnsExpected(int propertyID, object expected)
{
using var checkBox = new CheckBox
{
Name = "CheckBox1",
AccessibleName = "TestName"
};
Assert.False(checkBox.IsHandleCreated);
var checkBoxAccessibleObject = new CheckBox.CheckBoxAccessibleObject(checkBox);
object value = checkBoxAccessibleObject.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(checkBox.IsHandleCreated);
}
[WinFormsTheory]
[InlineData((int)UIA.TogglePatternId)]
[InlineData((int)UIA.LegacyIAccessiblePatternId)]
public void CheckBoxAccessibleObject_IsPatternSupported_Invoke_ReturnsExpected(int patternId)
{
using var checkBox = new CheckBox();
Assert.False(checkBox.IsHandleCreated);
var checkBoxAccessibleObject = new CheckBox.CheckBoxAccessibleObject(checkBox);
Assert.True(checkBoxAccessibleObject.IsPatternSupported((UIA)patternId));
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.True(checkBox.IsHandleCreated);
}
[WinFormsFact]
public void CheckBoxAccessibleObject_Toggle_Invoke_Success()
{
using var checkBox = new CheckBox();
Assert.False(checkBox.IsHandleCreated);
var checkBoxAccessibleObject = new CheckBox.CheckBoxAccessibleObject(checkBox);
Assert.False(checkBox.Checked);
checkBoxAccessibleObject.Toggle();
Assert.True(checkBox.Checked);
// toggle again
checkBoxAccessibleObject.Toggle();
Assert.False(checkBox.Checked);
// TODO: ControlAccessibleObject shouldn't force handle creation, tracked in https://github.com/dotnet/winforms/issues/3062
Assert.True(checkBox.IsHandleCreated);
}
}
}

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

@ -9,6 +9,7 @@ using Xunit;
namespace System.Windows.Forms.Tests
{
using static Interop.UiaCore;
using Point = System.Drawing.Point;
using Size = System.Drawing.Size;
@ -415,6 +416,24 @@ namespace System.Windows.Forms.Tests
Assert.False(control.GetTopLevel());
}
[WinFormsFact]
public void CheckBox_RaiseAutomationEvent_Invoke_Success()
{
using var checkBox = new TestCheckBox();
Assert.False(checkBox.IsHandleCreated);
var accessibleObject = (SubCheckBoxAccessibleObject)checkBox.AccessibilityObject;
Assert.Equal(0, accessibleObject.RaiseAutomationEventCallsCount);
Assert.Equal(0, accessibleObject.RaiseAutomationPropertyChangedEventCallsCount);
checkBox.Checked = true;
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(checkBox.IsHandleCreated);
}
// the zero here may be an issue with cultural variance
[WinFormsFact]
public void CheckBox_ToStringTest()
@ -495,5 +514,38 @@ namespace System.Windows.Forms.Tests
public new void OnClick(EventArgs e) => base.OnClick(e);
}
private class TestCheckBox : CheckBox
{
protected override AccessibleObject CreateAccessibilityInstance()
{
return new SubCheckBoxAccessibleObject(this);
}
}
private class SubCheckBoxAccessibleObject : CheckBox.CheckBoxAccessibleObject
{
public SubCheckBoxAccessibleObject(CheckBox 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);
}
}
}
}