diff --git a/src/System.Windows.Forms.Primitives/src/Interop/UiaCore/Interop.IMultipleViewProvider.cs b/src/System.Windows.Forms.Primitives/src/Interop/UiaCore/Interop.IMultipleViewProvider.cs new file mode 100644 index 00000000000..4435be67558 --- /dev/null +++ b/src/System.Windows.Forms.Primitives/src/Interop/UiaCore/Interop.IMultipleViewProvider.cs @@ -0,0 +1,28 @@ +// 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; + +internal static partial class Interop +{ + internal static partial class UiaCore + { + [ComImport] + [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(); + } + } +} diff --git a/src/System.Windows.Forms/src/Resources/SR.resx b/src/System.Windows.Forms/src/Resources/SR.resx index d7e3457f7c3..71699358051 100644 --- a/src/System.Windows.Forms/src/Resources/SR.resx +++ b/src/System.Windows.Forms/src/Resources/SR.resx @@ -6617,4 +6617,7 @@ Stack trace where the illegal operation occurred was: Error provider + + Double Click + diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.cs.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.cs.xlf index f91297a75de..1d52f63d18b 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.cs.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.cs.xlf @@ -192,6 +192,11 @@ Sbalit + + Double Click + Double Click + + Expand Rozbalit diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.de.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.de.xlf index 322931e55d4..fc7e7633cae 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.de.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.de.xlf @@ -192,6 +192,11 @@ Zuklappen + + Double Click + Double Click + + Expand Aufklappen diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.es.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.es.xlf index ac9527cdab8..6f91f9a81cf 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.es.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.es.xlf @@ -192,6 +192,11 @@ Contraer + + Double Click + Double Click + + Expand Expandir diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.fr.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.fr.xlf index 27540207724..1ae841ef38c 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.fr.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.fr.xlf @@ -192,6 +192,11 @@ Réduire + + Double Click + Double Click + + Expand Développer diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.it.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.it.xlf index 2b5dec53659..a71f51d790e 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.it.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.it.xlf @@ -192,6 +192,11 @@ Comprimi + + Double Click + Double Click + + Expand Espandi diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.ja.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.ja.xlf index 5b6938e6658..d8f63271592 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.ja.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.ja.xlf @@ -192,6 +192,11 @@ 折りたたむ + + Double Click + Double Click + + Expand 展開 diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.ko.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.ko.xlf index b310740138a..ab43687653a 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.ko.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.ko.xlf @@ -192,6 +192,11 @@ 축소 + + Double Click + Double Click + + Expand 확장 diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.pl.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.pl.xlf index 76f9b8707b7..eb2fc03121a 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.pl.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.pl.xlf @@ -192,6 +192,11 @@ Zwiń + + Double Click + Double Click + + Expand Rozwiń diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.pt-BR.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.pt-BR.xlf index cb4a7a81eb7..9cd1fc54a41 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.pt-BR.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.pt-BR.xlf @@ -192,6 +192,11 @@ Recolher + + Double Click + Double Click + + Expand Expandir diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.ru.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.ru.xlf index e48568a4fc4..cb79c03ef2d 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.ru.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.ru.xlf @@ -192,6 +192,11 @@ Свернуть + + Double Click + Double Click + + Expand Развертывание diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.tr.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.tr.xlf index bee44d905bc..43536f16706 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.tr.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.tr.xlf @@ -192,6 +192,11 @@ Daralt + + Double Click + Double Click + + Expand Genişlet diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.zh-Hans.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.zh-Hans.xlf index 505654e52a7..9be7a575394 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.zh-Hans.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.zh-Hans.xlf @@ -192,6 +192,11 @@ 折叠 + + Double Click + Double Click + + Expand 展开 diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.zh-Hant.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.zh-Hant.xlf index 7f912da3b35..2b56d9daf84 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.zh-Hant.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.zh-Hant.xlf @@ -192,6 +192,11 @@ 摺疊 + + Double Click + Double Click + + Expand 展開 diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/AccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/AccessibleObject.cs index 6135993d47f..b68d98585ea 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/AccessibleObject.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/AccessibleObject.cs @@ -45,7 +45,8 @@ public class AccessibleObject : UiaCore.ISelectionProvider, UiaCore.ISelectionItemProvider, UiaCore.IRawElementProviderHwndOverride, - UiaCore.IScrollItemProvider + UiaCore.IScrollItemProvider, + UiaCore.IMultipleViewProvider { /// /// Specifies the interface used by this . @@ -680,6 +681,16 @@ internal virtual void SetValue(string newValue) internal virtual UiaCore.IRawElementProviderSimple GetOverrideProviderForHwnd(IntPtr hwnd) => null; + internal virtual int GetMultiViewProviderCurrentView() => 0; + + internal virtual int[] GetMultiViewProviderSupportedViews() => new int[0]; + + internal virtual string GetMultiViewProviderViewName(int viewId) => null; + + internal virtual void SetMultiViewProviderCurrentView(int viewId) + { + } + internal virtual void SetValue(double newValue) { } @@ -2182,6 +2193,14 @@ object IReflect.InvokeMember(string name, BindingFlags invokeAttr, Binder binder UiaCore.IRawElementProviderSimple UiaCore.IRawElementProviderHwndOverride.GetOverrideProviderForHwnd(IntPtr hwnd) => GetOverrideProviderForHwnd(hwnd); + int UiaCore.IMultipleViewProvider.CurrentView => GetMultiViewProviderCurrentView(); + + int[] UiaCore.IMultipleViewProvider.GetSupportedViews() => GetMultiViewProviderSupportedViews(); + + string UiaCore.IMultipleViewProvider.GetViewName(int viewId) => GetMultiViewProviderViewName(viewId); + + void UiaCore.IMultipleViewProvider.SetCurrentView(int viewId) => SetMultiViewProviderCurrentView(viewId); + BOOL UiaCore.IRangeValueProvider.IsReadOnly => IsReadOnly ? BOOL.TRUE : BOOL.FALSE; double UiaCore.IRangeValueProvider.LargeChange => LargeChange; @@ -2541,7 +2560,8 @@ internal sealed class InternalAccessibleObject : UiaCore.ISelectionProvider, UiaCore.ISelectionItemProvider, UiaCore.IScrollItemProvider, - UiaCore.IRawElementProviderHwndOverride + UiaCore.IRawElementProviderHwndOverride, + UiaCore.IMultipleViewProvider { private IAccessible publicIAccessible; // AccessibleObject as IAccessible private readonly OleAut32.IEnumVariant publicIEnumVariant; // AccessibleObject as IEnumVariant @@ -2569,6 +2589,7 @@ internal sealed class InternalAccessibleObject : private readonly UiaCore.ISelectionItemProvider publicISelectionItemProvider; // AccessibleObject as ISelectionItemProvider private readonly UiaCore.IScrollItemProvider publicIScrollItemProvider; // AccessibleObject as IScrollItemProvider private readonly UiaCore.IRawElementProviderHwndOverride publicIRawElementProviderHwndOverride; // AccessibleObject as IRawElementProviderHwndOverride + private readonly UiaCore.IMultipleViewProvider publicIMultiViewProvider; // AccessibleObject as IMultipleViewProvider /// /// Create a new wrapper. @@ -2599,6 +2620,7 @@ internal InternalAccessibleObject(AccessibleObject accessibleImplemention) publicISelectionItemProvider = (UiaCore.ISelectionItemProvider)accessibleImplemention; publicIScrollItemProvider = (UiaCore.IScrollItemProvider)accessibleImplemention; publicIRawElementProviderHwndOverride = (UiaCore.IRawElementProviderHwndOverride)accessibleImplemention; + publicIMultiViewProvider = (UiaCore.IMultipleViewProvider)accessibleImplemention; // Note: Deliberately not holding onto AccessibleObject to enforce all access through the interfaces } @@ -2846,6 +2868,10 @@ object UiaCore.IRawElementProviderSimple.GetPatternProvider(UiaCore.UIA patternI { return (UiaCore.IScrollItemProvider)this; } + else if (patternId == UiaCore.UIA.MultipleViewPatternId) + { + return (UiaCore.IMultipleViewProvider)this; + } else { return null; @@ -3043,5 +3069,17 @@ UiaCore.IRawElementProviderSimple UiaCore.ISelectionItemProvider.SelectionContai /// Return the provider for the specified component, or null if the component is not being overridden. UiaCore.IRawElementProviderSimple UiaCore.IRawElementProviderHwndOverride.GetOverrideProviderForHwnd(IntPtr hwnd) => publicIRawElementProviderHwndOverride.GetOverrideProviderForHwnd(hwnd); + + int UiaCore.IMultipleViewProvider.CurrentView + => publicIMultiViewProvider.CurrentView; + + int[] UiaCore.IMultipleViewProvider.GetSupportedViews() + => publicIMultiViewProvider.GetSupportedViews(); + + string UiaCore.IMultipleViewProvider.GetViewName(int viewId) + => publicIMultiViewProvider.GetViewName(viewId); + + void UiaCore.IMultipleViewProvider.SetCurrentView(int viewId) + => publicIMultiViewProvider.SetCurrentView(viewId); } } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Button.ButtonAccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Button.ButtonAccessibleObject.cs new file mode 100644 index 00000000000..473732fcb44 --- /dev/null +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Button.ButtonAccessibleObject.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using static System.Windows.Forms.Control; +using static Interop; + +namespace System.Windows.Forms +{ + public partial class Button + { + [ComVisible(true)] + internal class ButtonAccessibleObject : ControlAccessibleObject + { + internal ButtonAccessibleObject(Button owner) : base(owner) + { + } + + private Button OwningButton => Owner as Button; + + internal override object GetPropertyValue(UiaCore.UIA propertyID) + { + switch (propertyID) + { + case UiaCore.UIA.NamePropertyId: + return Name; + case UiaCore.UIA.AutomationIdPropertyId: + return OwningButton.IsHandleCreated ? OwningButton.Name : String.Empty; + case UiaCore.UIA.ControlTypePropertyId: + return UiaCore.UIA.ButtonControlTypeId; + case UiaCore.UIA.IsKeyboardFocusablePropertyId: + // This is necessary for compatibility with MSAA proxy: + // IsKeyboardFocusable = true regardless the control is enabled/disabled. + return true; + } + + return base.GetPropertyValue(propertyID); + } + } + } +} diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Button.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Button.cs index 76e89f76731..15470bef4f3 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Button.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Button.cs @@ -22,7 +22,7 @@ namespace System.Windows.Forms [ClassInterface(ClassInterfaceType.AutoDispatch)] [SRDescription(nameof(SR.DescriptionButton))] [Designer("System.Windows.Forms.Design.ButtonBaseDesigner, " + AssemblyRef.SystemDesign)] - public class Button : ButtonBase, IButtonControl + public partial class Button : ButtonBase, IButtonControl { /// /// The dialog result that will be sent to the parent dialog form when @@ -387,5 +387,8 @@ protected override void WndProc(ref Message m) break; } } + + protected override AccessibleObject CreateAccessibilityInstance() + => new ButtonAccessibleObject(this); } } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ButtonBase.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ButtonBase.cs index 7ea984f6899..634088cfdfa 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/ButtonBase.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/ButtonBase.cs @@ -753,11 +753,6 @@ private void Animate(bool animate) } } - protected override AccessibleObject CreateAccessibilityInstance() - { - return new ButtonBaseAccessibleObject(this); - } - private void DetachImageList(object sender, EventArgs e) { ImageList = null; @@ -1327,34 +1322,5 @@ protected override void WndProc(ref Message m) } } } - - [ComVisible(true)] - public class ButtonBaseAccessibleObject : ControlAccessibleObject - { - public ButtonBaseAccessibleObject(Control owner) : base(owner) - { - } - - public override void DoDefaultAction() - { - ((ButtonBase)Owner).OnClick(EventArgs.Empty); - } - - public override AccessibleStates State - { - get - { - AccessibleStates state = base.State; - - ButtonBase owner = (ButtonBase)Owner; - if (owner.OwnerDraw && owner.MouseIsDown) - { - state |= AccessibleStates.Pressed; - } - - return state; - } - } - } } } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/CheckBox.CheckBoxAccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/CheckBox.CheckBoxAccessibleObject.cs new file mode 100644 index 00000000000..04a28bddd5c --- /dev/null +++ b/src/System.Windows.Forms/src/System/Windows/Forms/CheckBox.CheckBoxAccessibleObject.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using static System.Windows.Forms.Control; +using static Interop; + +namespace System.Windows.Forms +{ + public partial class CheckBox + { + [ComVisible(true)] + internal class CheckBoxAccessibleObject : ControlAccessibleObject + { + internal CheckBoxAccessibleObject(CheckBox owner) : base(owner) + { + } + + private CheckBox OwningCheckBox => Owner as CheckBox; + + internal override bool IsPatternSupported(UiaCore.UIA patternId) + { + if (patternId == UiaCore.UIA.TogglePatternId) + { + return true; + } + + return base.IsPatternSupported(patternId); + } + + internal override object GetPropertyValue(UiaCore.UIA propertyID) + { + switch (propertyID) + { + case UiaCore.UIA.NamePropertyId: + return Name; + case UiaCore.UIA.AutomationIdPropertyId: + return OwningCheckBox.IsHandleCreated ? OwningCheckBox.Name : String.Empty; + case UiaCore.UIA.IsTogglePatternAvailablePropertyId: + return IsPatternSupported(UiaCore.UIA.TogglePatternId); + case UiaCore.UIA.ControlTypePropertyId: + return UiaCore.UIA.CheckBoxControlTypeId; + case UiaCore.UIA.IsKeyboardFocusablePropertyId: + // This is necessary for compatibility with MSAA proxy: + // IsKeyboardFocusable = true regardless the control is enabled/disabled. + return true; + } + + return base.GetPropertyValue(propertyID); + } + + public override string DefaultAction + { + get + { + string defaultAction = Owner.AccessibleDefaultActionDescription; + if (defaultAction != null) + { + return defaultAction; + } + + if (OwningCheckBox.Checked) + { + return SR.AccessibleActionUncheck; + } + else + { + return SR.AccessibleActionCheck; + } + } + } + + internal override UiaCore.ToggleState ToggleState + { + get + { + return OwningCheckBox.Checked ? UiaCore.ToggleState.On : UiaCore.ToggleState.Off; + } + } + } + } +} diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/CheckBox.cs b/src/System.Windows.Forms/src/System/Windows/Forms/CheckBox.cs index d930a0888da..8b6380ca201 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/CheckBox.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/CheckBox.cs @@ -25,7 +25,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(); @@ -534,7 +534,6 @@ protected override void OnClick(EventArgs e) if (threeState) { CheckState = CheckState.Indeterminate; - // If the check box is clicked as a result of AccObj::DoDefaultAction // then the native check box does not fire OBJ_STATE_CHANGE event when going to Indeterminate state. // So the WinForms layer fires the OBJ_STATE_CHANGE event. @@ -647,85 +646,5 @@ public override string ToString() int checkState = (int)CheckState; return s + ", CheckState: " + checkState.ToString(CultureInfo.InvariantCulture); } - - [ComVisible(true)] - 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; - } - } - } - } } } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Control.ControlAccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Control.ControlAccessibleObject.cs index b1bb5395939..2d10ec3effa 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Control.ControlAccessibleObject.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Control.ControlAccessibleObject.cs @@ -26,6 +26,8 @@ public class ControlAccessibleObject : AccessibleObject private IntPtr _handle = IntPtr.Zero; // Associated window handle (if any) private int[] _runtimeId = null; // Used by UIAutomation + internal event EventHandler RequestingNavigationFragment; + public ControlAccessibleObject(Control ownerControl) { Owner = ownerControl ?? throw new ArgumentNullException(nameof(ownerControl)); @@ -368,6 +370,167 @@ public override AccessibleRole Role } } + internal class RequestingNavigationFragmentEventArgs : EventArgs + { + public RequestingNavigationFragmentEventArgs(UiaCore.NavigateDirection navigationDirection) + { + NavigationDirection = navigationDirection; + } + + public UiaCore.IRawElementProviderFragment RequestingNavigationFragment { get; set; } + + public UiaCore.NavigateDirection NavigationDirection { get; private set; } + } + + internal override UiaCore.IRawElementProviderFragment FragmentNavigate(UiaCore.NavigateDirection direction) + { + switch (direction) + { + case UiaCore.NavigateDirection.Parent: + case UiaCore.NavigateDirection.NextSibling: + case UiaCore.NavigateDirection.PreviousSibling: + var eventArgs = new RequestingNavigationFragmentEventArgs(direction); + RequestingNavigationFragment?.Invoke(this, eventArgs); + return eventArgs.RequestingNavigationFragment; + default: + return base.FragmentNavigate(direction); + } + } + + internal AccessibleObject GetNextChildInTabOrder(AccessibleObject currentChild) + { + var currentChildControlAccessibleObject = currentChild as ControlAccessibleObject; + if (currentChildControlAccessibleObject == null || currentChildControlAccessibleObject.Owner == null) + { + return null; + } + + return GetNextChildInTabOrder(currentChildControlAccessibleObject.Owner, true); + } + + internal AccessibleObject GetPreviousChildInTabOrder(AccessibleObject currentChild) + { + var currentChildControlAccessibleObject = currentChild as ControlAccessibleObject; + if (currentChildControlAccessibleObject == null || currentChildControlAccessibleObject.Owner == null) + { + return null; + } + + return GetNextChildInTabOrder(currentChildControlAccessibleObject.Owner, true); + } + + private static bool HaveSameTabIndex(ControlCollection controls) + { + if (controls.Count == 0 || controls.Count == 1) + { + return true; + } + + int firstTabIndex = controls[0]._tabIndex; + for(int i = 1; i < controls.Count; i++) + { + if (controls[i]._tabIndex != firstTabIndex) + { + return false; + } + } + + return true; + } + + internal AccessibleObject GetNextChildInTabOrder(Control currentChild, bool forward) + { + if (Owner == null || !Owner.IsHandleCreated) + { + return null; + } + + ControlCollection childControls = (ControlCollection)Owner.Properties.GetObject(s_controlsCollectionProperty); + if (childControls == null && childControls.Count == 0) + { + return null; + } + + Control nextChild = null; + if (forward) + { + for (int i = 0; i < childControls.Count; i++) + { + if (currentChild == null) + { + if (i < childControls.Count && + (nextChild == null || nextChild.TabIndex >= childControls[i].TabIndex)) + { + nextChild = childControls[i]; + } + + continue; + } + + if (childControls[i].Name == currentChild.Name) + { + continue; + } + + if (i < childControls.Count - 1 && + currentChild.Name == childControls[i + 1].Name && + currentChild.TabIndex == childControls[i].TabIndex) + { + nextChild = childControls[i]; + break; + } + + if ((childControls[i].TabIndex > currentChild.TabIndex) && + (nextChild == null || nextChild.TabIndex > childControls[i].TabIndex)) + { + nextChild = childControls[i]; + } + } + } + else + { + for (int i = childControls.Count - 1; i >= 0; i--) + { + if (currentChild == null) + { + if (i >= 0 && + (nextChild == null || nextChild.TabIndex <= childControls[i].TabIndex)) + { + nextChild = childControls[i]; + } + + continue; + } + + if (childControls[i].Name == currentChild.Name) + { + continue; + } + + if (i > 0 && + currentChild.Name == childControls[i - 1].Name && + currentChild.TabIndex == childControls[i].TabIndex) + { + nextChild = childControls[i]; + break; + } + + if ((childControls[i].TabIndex < currentChild.TabIndex) && + (nextChild == null || nextChild.TabIndex <= childControls[i].TabIndex)) + { + nextChild = childControls[i]; + } + } + } + + if (nextChild != null) + { + return nextChild.AccessibilityObject; + } + + return null; + } + public override int GetHelpTopic(out string fileName) { int topic = 0; @@ -437,8 +600,7 @@ public override bool RaiseLiveRegionChanged() return RaiseAutomationEvent(UiaCore.UIA.LiveRegionChangedEventId); } - internal override bool IsIAccessibleExSupported() - => Owner is IAutomationLiveRegion ? true : base.IsIAccessibleExSupported(); + internal override bool IsIAccessibleExSupported() => true; internal override object GetPropertyValue(UiaCore.UIA propertyID) { diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs index 4bd4464e465..b3f65dee115 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs @@ -5924,6 +5924,10 @@ private ArrayList GetChildWindowsTabOrderList() return holders; } + internal Control FirstChildControlInTabOrder => GetFirstChildControlInTabOrder(true); + + internal Control LastChildControlInTabOrder => GetFirstChildControlInTabOrder(false); + internal virtual Control GetFirstChildControlInTabOrder(bool forward) { ControlCollection ctlControls = (ControlCollection)Properties.GetObject(s_controlsCollectionProperty); @@ -13364,7 +13368,7 @@ internal virtual bool SupportsUiaProviders { get { - return false; + return true; } } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Form.AccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Form.AccessibleObject.cs index ea1908f73c8..1b0e2afc20e 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Form.AccessibleObject.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Form.AccessibleObject.cs @@ -29,6 +29,19 @@ internal FormAccessibleObject(Form owner) : base(owner) internal override Rectangle BoundingRectangle => _owner.Bounds; + internal override UiaCore.IRawElementProviderFragment FragmentNavigate(UiaCore.NavigateDirection direction) + { + switch (direction) + { + case UiaCore.NavigateDirection.FirstChild: + return GetNextChildInTabOrder(null, true); + case UiaCore.NavigateDirection.LastChild: + return GetNextChildInTabOrder(null, false); + default: + return null; + } + } + internal override bool IsIAccessibleExSupported() { if (_owner != null) diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Form.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Form.cs index f8e98eb6e5e..d5d37c4b331 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Form.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Form.cs @@ -3865,6 +3865,50 @@ protected virtual void OnClosed(EventArgs e) ((EventHandler)Events[EVENT_CLOSED])?.Invoke(this, e); } + protected override void OnControlAdded(ControlEventArgs e) + { + base.OnControlAdded(e); + + ControlAccessibleObject controlAccessibleObject = e.Control.AccessibilityObject as ControlAccessibleObject; + if (controlAccessibleObject != null) + { + controlAccessibleObject.RequestingNavigationFragment += ControlAccessibleObject_RequestingNavigationFragment; + } + } + + protected override void OnControlRemoved(ControlEventArgs e) + { + base.OnControlRemoved(e); + + ControlAccessibleObject controlAccessibleObject = e.Control.AccessibilityObject as ControlAccessibleObject; + if (controlAccessibleObject != null) + { + controlAccessibleObject.RequestingNavigationFragment -= ControlAccessibleObject_RequestingNavigationFragment; + } + } + + private void ControlAccessibleObject_RequestingNavigationFragment(object sender, ControlAccessibleObject.RequestingNavigationFragmentEventArgs e) + { + switch (e.NavigationDirection) + { + case UiaCore.NavigateDirection.Parent: + e.RequestingNavigationFragment = AccessibilityObject; + break; + case UiaCore.NavigateDirection.NextSibling: + case UiaCore.NavigateDirection.PreviousSibling: + bool forward = e.NavigationDirection == UiaCore.NavigateDirection.NextSibling; + var controlAccessibleObject = AccessibilityObject as ControlAccessibleObject; + var senderControlAccessibleObject = sender as ControlAccessibleObject; + if (controlAccessibleObject != null && senderControlAccessibleObject != null) + { + var nextControlAccessibleObject = controlAccessibleObject.GetNextChildInTabOrder(senderControlAccessibleObject.Owner, forward); + e.RequestingNavigationFragment = nextControlAccessibleObject; + } + + break; + } + } + /// /// The Closing event is fired before the form is closed. /// diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/GroupBox.GroupBoxAccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/GroupBox.GroupBoxAccessibleObject.cs new file mode 100644 index 00000000000..9056337389b --- /dev/null +++ b/src/System.Windows.Forms/src/System/Windows/Forms/GroupBox.GroupBoxAccessibleObject.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using static Interop; + +namespace System.Windows.Forms +{ + public partial class GroupBox + { + [ComVisible(true)] + internal class GroupBoxAccessibleObject : ControlAccessibleObject + { + internal GroupBoxAccessibleObject(GroupBox owner) : base(owner) + { + } + + private GroupBox OwningGroupBox => Owner as GroupBox; + + internal override object GetPropertyValue(UiaCore.UIA propertyID) + { + switch (propertyID) + { + case UiaCore.UIA.NamePropertyId: + return Name; + case UiaCore.UIA.ControlTypePropertyId: + return UiaCore.UIA.GroupControlTypeId; + case UiaCore.UIA.AutomationIdPropertyId: + return OwningGroupBox.IsHandleCreated ? OwningGroupBox.Name : String.Empty; + case UiaCore.UIA.IsKeyboardFocusablePropertyId: + return true; + } + + return base.GetPropertyValue(propertyID); + } + } + } +} diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/GroupBox.cs b/src/System.Windows.Forms/src/System/Windows/Forms/GroupBox.cs index a90069afd20..472d840af33 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/GroupBox.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/GroupBox.cs @@ -25,7 +25,7 @@ namespace System.Windows.Forms [DefaultProperty(nameof(Text))] [Designer("System.Windows.Forms.Design.GroupBoxDesigner, " + AssemblyRef.SystemDesign)] [SRDescription(nameof(SR.DescriptionGroupBox))] - public class GroupBox : Control + public partial class GroupBox : Control { int fontHeight = -1; Font cachedFont; @@ -753,41 +753,5 @@ protected override AccessibleObject CreateAccessibilityInstance() { return new GroupBoxAccessibleObject(this); } - - [ComVisible(true)] - internal class GroupBoxAccessibleObject : ControlAccessibleObject - { - internal GroupBoxAccessibleObject(GroupBox owner) : base(owner) - { - } - - public override AccessibleRole Role - { - get - { - AccessibleRole role = Owner.AccessibleRole; - if (role != AccessibleRole.Default) - { - return role; - } - return AccessibleRole.Grouping; - } - } - - internal override bool IsIAccessibleExSupported() => true; - - internal override object GetPropertyValue(UiaCore.UIA propertyID) - { - switch (propertyID) - { - case UiaCore.UIA.ControlTypePropertyId: - return UiaCore.UIA.GroupControlTypeId; - case UiaCore.UIA.IsKeyboardFocusablePropertyId: - return true; - } - - return base.GetPropertyValue(propertyID); - } - } } } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Label.LabelAccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Label.LabelAccessibleObject.cs new file mode 100644 index 00000000000..acb1f0e2a0b --- /dev/null +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Label.LabelAccessibleObject.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using static Interop; + +namespace System.Windows.Forms +{ + public partial class Label + { + [ComVisible(true)] + internal class LabelAccessibleObject : ControlAccessibleObject + { + public LabelAccessibleObject(Label owner) : base(owner) + { + } + + private Label OwningLabel => Owner as Label; + + internal override object GetPropertyValue(UiaCore.UIA propertyID) + { + switch (propertyID) + { + case UiaCore.UIA.NamePropertyId: + return Name; + case UiaCore.UIA.AutomationIdPropertyId: + return OwningLabel.IsHandleCreated ? OwningLabel.Name : String.Empty; + case UiaCore.UIA.ControlTypePropertyId: + return UiaCore.UIA.TextControlTypeId; + } + + return base.GetPropertyValue(propertyID); + } + } + } +} diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Label.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Label.cs index 0771878fa96..88ab5b6bd53 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Label.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Label.cs @@ -28,7 +28,7 @@ namespace System.Windows.Forms [ToolboxItem("System.Windows.Forms.Design.AutoSizeToolboxItem," + AssemblyRef.SystemDesign)] [SRDescription(nameof(SR.DescriptionLabel))] // If not for FormatControl, we could inherit from ButtonBase and get foreground images for free. - public class Label : Control, IAutomationLiveRegion + public partial class Label : Control, IAutomationLiveRegion { private static readonly object EVENT_TEXTALIGNCHANGED = new object(); @@ -1643,95 +1643,62 @@ protected override void WndProc(ref Message m) } } - [ComVisible(true)] - internal class LabelAccessibleObject : ControlAccessibleObject + /// + /// Override ImageList.Indexer to support Label's ImageList semantics. + /// + internal class LabelImageIndexer : ImageList.Indexer { - public LabelAccessibleObject(Label owner) : base(owner) + private readonly Label owner; + private bool useIntegerIndex = true; + + public LabelImageIndexer(Label owner) { + this.owner = owner; } - public override AccessibleRole Role + public override ImageList ImageList { - get - { - AccessibleRole role = Owner.AccessibleRole; - if (role != AccessibleRole.Default) - { - return role; - } - return AccessibleRole.StaticText; - } + get { return owner?.ImageList; } + set { Debug.Assert(false, "Setting the image list in this class is not supported"); } } - internal override bool IsIAccessibleExSupported() => true; - - internal override object GetPropertyValue(UiaCore.UIA propertyID) + public override string Key { - if (propertyID == UiaCore.UIA.ControlTypePropertyId) + get => base.Key; + set { - return UiaCore.UIA.TextControlTypeId; + base.Key = value; + useIntegerIndex = false; } - - return base.GetPropertyValue(propertyID); } - } - } - /// - /// Override ImageList.Indexer to support Label's ImageList semantics. - /// - internal class LabelImageIndexer : ImageList.Indexer - { - private readonly Label owner; - private bool useIntegerIndex = true; - - public LabelImageIndexer(Label owner) - { - this.owner = owner; - } - - public override ImageList ImageList - { - get { return owner?.ImageList; } - set{ Debug.Assert(false, "Setting the image list in this class is not supported"); } - } - - public override string Key - { - get => base.Key; - set + public override int Index { - base.Key = value; - useIntegerIndex = false; - } - } - - public override int Index - { - get => base.Index; - set - { - base.Index = value; - useIntegerIndex = true; + get => base.Index; + set + { + base.Index = value; + useIntegerIndex = true; + } } - } - public override int ActualIndex - { - get + public override int ActualIndex { - if (useIntegerIndex) - { - // The behavior of label is to return the last item in the Images collection - // if the index is currently set higher than the count. - return (Index < ImageList.Images.Count) ? Index : ImageList.Images.Count - 1; - } - else if (ImageList != null) + get { - return ImageList.Images.IndexOfKey(Key); - } + if (useIntegerIndex) + { + // The behavior of label is to return the last item in the Images collection + // if the index is currently set higher than the count. + return (Index < ImageList.Images.Count) ? Index : ImageList.Images.Count - 1; + } + else if (ImageList != null) + { + return ImageList.Images.IndexOfKey(Key); + } - return -1; + return -1; + } } } } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ListView.ListViewAccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ListView.ListViewAccessibleObject.cs new file mode 100644 index 00000000000..0ff7c09bdb7 --- /dev/null +++ b/src/System.Windows.Forms/src/System/Windows/Forms/ListView.ListViewAccessibleObject.cs @@ -0,0 +1,260 @@ +// 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. + +#nullable disable + +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Runtime.InteropServices; +using Accessibility; +using static Interop; +using static Interop.UiaCore; + +namespace System.Windows.Forms +{ + public partial class ListView + { + [ComVisible(true)] + internal class ListViewAccessibleObject : ControlAccessibleObject + { + private readonly ListView owner; + private readonly Dictionary _itemAccessibleObjects; + private readonly IAccessible _systemIAccessible; + + internal ListViewAccessibleObject(ListView owner) : base(owner) + { + this.owner = owner; + _itemAccessibleObjects = new Dictionary(); + _systemIAccessible = GetSystemIAccessibleInternal(); + } + + internal override UiaCore.IRawElementProviderFragmentRoot FragmentRoot => this; + + private ListView OwningListView => Owner as ListView; + + internal override bool IsPatternSupported(UIA patternId) + { + if (patternId == UIA.SelectionPatternId || + patternId == UIA.MultipleViewPatternId || + patternId == UIA.LegacyIAccessiblePatternId || + (patternId == UIA.GridPatternId && OwningListView.View == View.Details) || + (patternId == UIA.TablePatternId && OwningListView.View == View.Details)) + { + return true; + } + + return base.IsPatternSupported(patternId); + } + + internal override IRawElementProviderFragment FragmentNavigate(UiaCore.NavigateDirection direction) + { + int childCount = OwningListView.Items.Count; + + if (childCount == 0) + { + return null; + } + + switch (direction) + { + case NavigateDirection.FirstChild: + return GetChild(0); + case NavigateDirection.LastChild: + return GetChild(childCount - 1); + default: + return base.FragmentNavigate(direction); + } + } + + internal override bool CanSelectMultiple => true; + + internal override object GetPropertyValue(UIA propertyID) + { + switch (propertyID) + { + case UIA.NamePropertyId: + return Name; + case UIA.AutomationIdPropertyId: + return OwningListView.IsHandleCreated ? OwningListView.Name : string.Empty; + case UIA.RuntimeIdPropertyId: + return RuntimeId; + case UIA.ControlTypePropertyId: + return UIA.ListControlTypeId; + case UIA.IsMultipleViewPatternAvailablePropertyId: + return IsPatternSupported(UIA.MultipleViewPatternId); + case UIA.ItemStatusPropertyId: + return GetItemStatus(); + } + + return base.GetPropertyValue(propertyID); + } + + internal override int[] RuntimeId + { + get + { + var runtimeId = new int[2]; + runtimeId[0] = 0x2a; + runtimeId[1] = (int)(long)OwningListView.Handle; + return runtimeId; + } + } + + private string GetItemStatus() + { + switch (owner.Sorting) + { + case SortOrder.None: + return SR.NotSortedAccessibleStatus; + case SortOrder.Ascending: + return SR.SortedAscendingAccessibleStatus; + case SortOrder.Descending: + return SR.SortedDescendingAccessibleStatus; + } + + return null; + } + + public override AccessibleObject GetChild(int index) + { + if (index < 0 || index >= OwningListView.Items.Count) + { + return null; + } + + ListViewItem item = OwningListView.Items[index]; + if (!_itemAccessibleObjects.ContainsKey(item)) + { + _itemAccessibleObjects.Add(item, new ListViewItemAccessibleObject(OwningListView, item, this)); + } + + return _itemAccessibleObjects[item]; + } + + public override int GetChildCount() + { + return OwningListView.IsHandleCreated ? OwningListView.Items.Count : 0; + } + + public Dictionary ItemAccessibleObjects + { + get + { + return _itemAccessibleObjects; + } + } + + internal override IRawElementProviderSimple[] GetSelection() + { + List selectedItemProviders = new List(); + + if (!OwningListView.IsHandleCreated) + { + return selectedItemProviders.ToArray(); + } + + var selectedItems = OwningListView.SelectedItems; + foreach (var selectedItem in selectedItems) + { + selectedItemProviders.Add(_itemAccessibleObjects[(ListViewItem)selectedItem] as IRawElementProviderSimple); + } + + return selectedItemProviders.ToArray(); + } + + internal override IRawElementProviderFragment ElementProviderFromPoint(double x, double y) + { + return HitTest((int)x, (int)y); + } + + public override AccessibleObject HitTest(int x, int y) + { + Point pt = owner.PointToClient(new Point(x, y)); + ListViewHitTestInfo hti = owner.HitTest(pt.X, pt.Y); + if (!_itemAccessibleObjects.ContainsKey(hti.Item)) + { + _itemAccessibleObjects.Add(hti.Item, new ListViewItemAccessibleObject(OwningListView, hti.Item, this)); + } + return ItemAccessibleObjects[hti.Item].SubItemAccessibleObjects[hti.SubItem]; + } + + internal override int ColumnCount + { + get + { + int columnCount = 0; + + var items = OwningListView.Items; + if (items.Count > 0) + { + columnCount = items[0].SubItems.Count; + } + + return columnCount; + } + } + + internal override int RowCount => OwningListView.Items.Count; + + internal override IRawElementProviderSimple[] GetRowHeaders() => null; + + internal override IRawElementProviderSimple[] GetColumnHeaders() + { + var columnHeaders = new List(); + foreach (var columnHeader in OwningListView.Columns) + { + columnHeaders.Add(new ListViewColumnAccessibleObject(columnHeader as ColumnHeader)); + } + + return columnHeaders.ToArray(); + } + + internal override RowOrColumnMajor RowOrColumnMajor => RowOrColumnMajor.RowMajor; + + internal override int GetMultiViewProviderCurrentView() + { + return (int)OwningListView.View; + } + + internal override int[] GetMultiViewProviderSupportedViews() + { + var allViews = Enum.GetValues(typeof(View)); + var allViewIds = new List(); + foreach (var view in allViews) + { + allViewIds.Add((int)view); + } + + return allViewIds.ToArray(); + } + + internal override string GetMultiViewProviderViewName(int viewId) + { + var allViews = Enum.GetValues(typeof(View)); + foreach (var view in allViews) + { + if ((int)view == viewId) + { + return view.ToString(); + } + } + + return null; + } + + internal override void SetMultiViewProviderCurrentView(int viewId) + { + var allViews = Enum.GetValues(typeof(View)); + foreach (var view in allViews) + { + if ((int)view == viewId) + { + OwningListView.View = (View)view; + } + } + } + } + } +} diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ListView.ListViewColumnAccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ListView.ListViewColumnAccessibleObject.cs new file mode 100644 index 00000000000..b8b23b8aa1c --- /dev/null +++ b/src/System.Windows.Forms/src/System/Windows/Forms/ListView.ListViewColumnAccessibleObject.cs @@ -0,0 +1,36 @@ +// 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. + +#nullable disable + +using static Interop.UiaCore; + +namespace System.Windows.Forms +{ + public partial class ListView + { + internal class ListViewColumnAccessibleObject : AccessibleObject + { + private ColumnHeader _columnHeader; + + public ListViewColumnAccessibleObject(ColumnHeader columnHeader) + { + _columnHeader = columnHeader; + } + + internal override object GetPropertyValue(UIA propertyID) + { + switch (propertyID) + { + case UIA.ControlTypePropertyId: + return UIA.HeaderItemControlTypeId; + case UIA.NamePropertyId: + return _columnHeader?.Text ?? string.Empty; + default: + return base.GetPropertyValue(propertyID); + } + } + } + } +} diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ListView.ListViewItemAccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ListView.ListViewItemAccessibleObject.cs new file mode 100644 index 00000000000..267b2142524 --- /dev/null +++ b/src/System.Windows.Forms/src/System/Windows/Forms/ListView.ListViewItemAccessibleObject.cs @@ -0,0 +1,355 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Accessibility; +using static Interop; +using static Interop.UiaCore; + +namespace System.Windows.Forms +{ + public partial class ListView + { + /// + /// ListBox control accessible object with UI Automation provider functionality. + /// This inherits from the base ListBoxExAccessibleObject and ListBoxAccessibleObject + /// to have all base functionality. + /// + [ComVisible(true)] + internal class ListViewItemAccessibleObject : AccessibleObject + { + private readonly ListViewItem _itemEntry; + private readonly ListViewAccessibleObject _owningAccessibleObject; + private readonly Dictionary _subItemAccessibleObjects; + private readonly ListView _owningListView; + private readonly IAccessible _systemIAccessible; + + + public ListViewItemAccessibleObject(ListView owningListView, object itemEntry, ListViewAccessibleObject owningAccessibleObject) + { + _owningListView = owningListView; + _itemEntry = (ListViewItem)itemEntry; + _owningAccessibleObject = owningAccessibleObject; + _subItemAccessibleObjects = new Dictionary(); + _systemIAccessible = owningAccessibleObject.GetSystemIAccessibleInternal(); + FillSubItemAccessibleObjectsCollection(); + } + + public override Rectangle Bounds + { + get + { + return new Rectangle( + _owningListView.AccessibilityObject.Bounds.X + _itemEntry.Bounds.X, + _owningListView.AccessibilityObject.Bounds.Y + _itemEntry.Bounds.Y, + _itemEntry.Bounds.Width, + _itemEntry.Bounds.Height); + } + } + + private string AutomationId + { + get + { + return string.Format("{0}-{1}", typeof(ListViewItem).Name, CurrentIndex); + } + } + + private int CurrentIndex => _owningListView.Items.IndexOf(_itemEntry); + + internal override UiaCore.IRawElementProviderFragmentRoot FragmentRoot => _owningAccessibleObject; + + internal override bool IsItemSelected + { + get + { + return (State & AccessibleStates.Selected) != 0; + } + } + + /// + /// Gets or sets the accessible name. + /// + public override string Name + { + get + { + return _itemEntry.Text; + } + set => base.Name = value; + } + + /// + /// Gets the accessible role. + /// + public override AccessibleRole Role => AccessibleRole.ListItem; + + /// + /// Gets the accessible state. + /// + public override AccessibleStates State + { + get + { + AccessibleStates state = AccessibleStates.Selectable | AccessibleStates.Focusable | AccessibleStates.MultiSelectable; + + if (_owningListView.SelectedItems.Contains(_itemEntry)) + { + return state |= AccessibleStates.Selected | AccessibleStates.Focused; + } + + return state |= (AccessibleStates)(_systemIAccessible.get_accState(GetChildId())); + } + } + + internal override void AddToSelection() + { + SelectItem(); + } + + public override string DefaultAction => SR.AccessibleActionDoubleClick; + + public override void DoDefaultAction() + { + SetFocus(); + } + + internal override IRawElementProviderFragment FragmentNavigate(NavigateDirection direction) + { + int firstItemIndex = 0; + int lastItemIndex = _owningListView.Items.Count - 1; + int currentIndex = CurrentIndex; + + switch (direction) + { + case NavigateDirection.Parent: + return _owningListView.AccessibilityObject; + case NavigateDirection.NextSibling: + var listViewAccessibilityObject = _owningListView.AccessibilityObject; + if (listViewAccessibilityObject != null) + { + int itemsCount = listViewAccessibilityObject.GetChildCount(); + int nextItemIndex = currentIndex + 1; + if (itemsCount > nextItemIndex) + { + return listViewAccessibilityObject.GetChild(nextItemIndex); + } + } + + break; + case NavigateDirection.PreviousSibling: + listViewAccessibilityObject = _owningListView.AccessibilityObject; + if (listViewAccessibilityObject != null) + { + int previousItemIndex = currentIndex - 1; + if (previousItemIndex >= 0) + { + return listViewAccessibilityObject.GetChild(previousItemIndex); + } + } + + break; + case NavigateDirection.FirstChild: + if (_itemEntry.SubItems.Count > 0) + { + return GetChild(firstItemIndex); + } + + return null; + case NavigateDirection.LastChild: + if (_itemEntry.SubItems.Count > 0) + { + return GetChild(lastItemIndex); + } + + return null; + } + + return null; + } + + private void FillSubItemAccessibleObjectsCollection() + { + foreach (var item in _itemEntry.SubItems) + { + _subItemAccessibleObjects.Add((ListViewItem.ListViewSubItem)item, new ListViewSubItemAccessibleObject(_owningListView, _itemEntry, (ListViewItem.ListViewSubItem)item)); + } + } + + public override AccessibleObject GetChild(int index) + { + if (index < 0 || index >= _itemEntry.SubItems.Count) + { + return null; + } + + ListViewItem.ListViewSubItem item = _itemEntry.SubItems[index]; + + return _subItemAccessibleObjects[item]; + } + + internal override int[] RuntimeId + { + get + { + var owningListViewRuntimeId = _owningListView.AccessibilityObject.RuntimeId; + + var runtimeId = new int[4]; + runtimeId[0] = owningListViewRuntimeId[0]; + runtimeId[1] = owningListViewRuntimeId[1]; + runtimeId[2] = 4; // Win32-like const. + runtimeId[3] = CurrentIndex; + return runtimeId; + } + } + + public Dictionary SubItemAccessibleObjects + { + get + { + return _subItemAccessibleObjects; + } + } + + internal override object GetPropertyValue(UIA propertyID) + { + switch (propertyID) + { + case UIA.RuntimeIdPropertyId: + return RuntimeId; + case UIA.AutomationIdPropertyId: + return AutomationId; + case UIA.BoundingRectanglePropertyId: + return Bounds; + case UIA.FrameworkIdPropertyId: + return "WinForm"; + case UIA.ControlTypePropertyId: + return UIA.ListItemControlTypeId; + case UIA.NamePropertyId: + return Name; + case UIA.HasKeyboardFocusPropertyId: + return _owningListView.Focused && _owningListView.FocusedItem == _itemEntry; + case UIA.IsKeyboardFocusablePropertyId: + return (State & AccessibleStates.Focusable) == AccessibleStates.Focusable; + case UIA.IsEnabledPropertyId: + return _owningListView.Enabled; + case UIA.IsOffscreenPropertyId: + return (State & AccessibleStates.Offscreen) == AccessibleStates.Offscreen; + case UIA.NativeWindowHandlePropertyId: + return _owningListView.Handle; + case UIA.IsSelectionItemPatternAvailablePropertyId: + return IsPatternSupported(UIA.SelectionItemPatternId); + case UIA.IsScrollItemPatternAvailablePropertyId: + return IsPatternSupported(UIA.ScrollItemPatternId); + case UIA.IsInvokePatternAvailablePropertyId: + return IsPatternSupported(UIA.InvokePatternId); + default: + return base.GetPropertyValue(propertyID); + } + } + + /// + /// Indicates whether specified pattern is supported. + /// + /// The pattern ID. + /// True if specified + internal override bool IsPatternSupported(UIA patternId) + { + if (patternId == UIA.ScrollItemPatternId || + patternId == UIA.LegacyIAccessiblePatternId || + patternId == UIA.SelectionItemPatternId || + patternId == UIA.InvokePatternId) + { + return true; + } + + return base.IsPatternSupported(patternId); + } + + internal override void RemoveFromSelection() + { + // Do nothing, C++ implementation returns UIA_E_INVALIDOPERATION 0x80131509 + } + + internal override IRawElementProviderSimple ItemSelectionContainer => _owningListView.AccessibilityObject; + + internal override void ScrollIntoView() + { + int currentIndex = CurrentIndex; + + if (_owningListView.SelectedItems == null) //no items selected + { + User32.SendMessageW(_owningListView, (User32.WM)User32.LB.SETCARETINDEX, (IntPtr)currentIndex); + return; + } + + int firstVisibleIndex = User32.SendMessageW(_owningListView, (User32.WM)User32.LB.GETTOPINDEX).ToInt32(); + if (currentIndex < firstVisibleIndex) + { + User32.SendMessageW(_owningListView, (User32.WM)User32.LB.SETTOPINDEX, (IntPtr)currentIndex); + return; + } + + int itemsHeightSum = 0; + int visibleItemsCount = 0; + int listBoxHeight = _owningListView.ClientRectangle.Height; + int itemsCount = _owningListView.Items.Count; + + for (int i = firstVisibleIndex; i < itemsCount; i++) + { + int itemHeight = User32.SendMessageW(_owningListView, (User32.WM)User32.LB.GETITEMHEIGHT, (IntPtr)i).ToInt32(); + + if ((itemsHeightSum += itemHeight) <= listBoxHeight) + { + continue; + } + + int lastVisibleIndex = i - 1; // - 1 because last "i" index is invisible + visibleItemsCount = lastVisibleIndex - firstVisibleIndex + 1; // + 1 because array indexes begin with 0 + + if (currentIndex > lastVisibleIndex) + { + User32.SendMessageW(_owningListView, (User32.WM)User32.LB.SETTOPINDEX, (IntPtr)(currentIndex - visibleItemsCount + 1)); + } + + break; + } + } + + internal unsafe override void SelectItem() + { + _owningListView.selectedIndexCollection.Add(CurrentIndex); + + User32.InvalidateRect(new HandleRef(this, _owningListView.Handle), null, BOOL.FALSE); + RaiseAutomationEvent(UIA.AutomationFocusChangedEventId); + RaiseAutomationEvent(UIA.SelectionItem_ElementSelectedEventId); + } + + internal override void SetFocus() + { + RaiseAutomationEvent(UIA.AutomationFocusChangedEventId); + SelectItem(); + } + + public override void Select(AccessibleSelection flags) + { + try + { + _systemIAccessible.accSelect((int)flags, GetChildId()); + } + catch (ArgumentException) + { + // In Everett, the ListBox accessible children did not have any selection capability. + // In Whidbey, they delegate the selection capability to OLEACC. + // However, OLEACC does not deal w/ several Selection flags: ExtendSelection, AddSelection, RemoveSelection. + // OLEACC instead throws an ArgumentException. + // Since Whidbey API's should not throw an exception in places where Everett API's did not, we catch + // the ArgumentException and fail silently. + } + } + } + } +} diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ListView.ListViewSubItemAccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ListView.ListViewSubItemAccessibleObject.cs new file mode 100644 index 00000000000..494d488a2d2 --- /dev/null +++ b/src/System.Windows.Forms/src/System/Windows/Forms/ListView.ListViewSubItemAccessibleObject.cs @@ -0,0 +1,204 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using static Interop; +using static Interop.UiaCore; + +namespace System.Windows.Forms +{ + public partial class ListView + { + [ComVisible(true)] + internal class ListViewSubItemAccessibleObject : AccessibleObject + { + private ListView _owningListView; + private ListViewItem _owningItem; + private ListViewItem.ListViewSubItem _owningSubItem; + + public ListViewSubItemAccessibleObject(ListView owningListView, ListViewItem owningItem, ListViewItem.ListViewSubItem owningSubItem) + { + _owningListView = owningListView ?? throw new ArgumentNullException(nameof(owningListView)); + _owningItem = owningItem ?? throw new ArgumentNullException(nameof(owningItem)); + _owningSubItem = owningSubItem ?? throw new ArgumentNullException(nameof(owningSubItem)); + } + + internal override IRawElementProviderFragmentRoot FragmentRoot + { + get + { + return _owningListView.AccessibilityObject; + } + } + + public override Rectangle Bounds + { + get + { + return new Rectangle( + _owningListView.AccessibilityObject.Bounds.X + _owningSubItem.Bounds.X, + _owningListView.AccessibilityObject.Bounds.Y + _owningSubItem.Bounds.Y, + _owningListView.Columns[GetCurrentIndex()].Width, + _owningSubItem.Bounds.Height); + } + } + + internal override IRawElementProviderFragment FragmentNavigate(NavigateDirection direction) + { + switch (direction) + { + case NavigateDirection.Parent: + return ParentFragment; + case NavigateDirection.NextSibling: + int nextSubItemIndex = GetCurrentIndex() + 1; + if (_owningItem.SubItems.Count > nextSubItemIndex) + { + var subItemCollection = (ParentFragment as ListViewItemAccessibleObject).SubItemAccessibleObjects; + var nextSubItem = _owningItem.SubItems[nextSubItemIndex]; + return subItemCollection[nextSubItem] as IRawElementProviderFragment; + } + + break; + case NavigateDirection.PreviousSibling: + int previousSubItemIndex = GetCurrentIndex() - 1; + if (previousSubItemIndex >= 0) + { + var subItemCollection = (ParentFragment as ListViewItemAccessibleObject).SubItemAccessibleObjects; + var previousSubItem = _owningItem.SubItems[previousSubItemIndex]; + return subItemCollection[previousSubItem] as IRawElementProviderFragment; + } + + break; + } + + return base.FragmentNavigate(direction); + } + + /// + /// Gets or sets the accessible name. + /// + public override string Name + { + get + { + return _owningSubItem.Text; + } + set => base.Name = value; + } + + internal override int[] RuntimeId + { + get + { + var itemCollection = (_owningListView.AccessibilityObject as ListViewAccessibleObject).ItemAccessibleObjects; + var itemUiaProvider = itemCollection[_owningItem] as IRawElementProviderFragment; + var owningItemRuntimeId = itemUiaProvider.GetRuntimeId(); + + var runtimeId = new int[5]; + runtimeId[0] = owningItemRuntimeId[0]; + runtimeId[1] = owningItemRuntimeId[1]; + runtimeId[2] = owningItemRuntimeId[2]; + runtimeId[3] = owningItemRuntimeId[3]; + runtimeId[4] = GetCurrentIndex(); + return runtimeId; + } + } + + internal override object GetPropertyValue(UIA propertyID) + { + switch (propertyID) + { + case UIA.ControlTypePropertyId: + return UIA.TextControlTypeId; + case UIA.NamePropertyId: + return Name; + case UIA.FrameworkIdPropertyId: + return "WinForm"; + case UIA.ProcessIdPropertyId: + return Process.GetCurrentProcess().Id; + case UIA.AutomationIdPropertyId: + return AutomationId; + case UIA.RuntimeIdPropertyId: + return RuntimeId; + case UIA.HasKeyboardFocusPropertyId: + return _owningListView.Focused && _owningListView.FocusedItem == _owningItem; + case UIA.IsKeyboardFocusablePropertyId: + return (State & AccessibleStates.Focusable) == AccessibleStates.Focusable; + case UIA.IsEnabledPropertyId: + return _owningListView.Enabled; + case UIA.IsOffscreenPropertyId: + return (State & AccessibleStates.Offscreen) == AccessibleStates.Offscreen; + case UIA.BoundingRectanglePropertyId: + return Bounds; + case UIA.IsGridItemPatternAvailablePropertyId: + return IsPatternSupported(UIA.GridItemPatternId); + case UIA.IsTableItemPatternAvailablePropertyId: + return IsPatternSupported(UIA.TableItemPatternId); + default: + return base.GetPropertyValue(propertyID); + } + } + + /// + /// Gets the accessible state. + /// + public override AccessibleStates State + { + get + { + return AccessibleStates.Focusable; + } + } + + internal override IRawElementProviderSimple ContainingGrid => _owningListView.AccessibilityObject; + + internal override int Row => _owningItem.Index; + + internal override int Column => _owningItem.SubItems.IndexOf(_owningSubItem); + + internal override IRawElementProviderSimple[] GetColumnHeaderItems() + { + var columnHeaders = new List(); + columnHeaders.Add(new ListViewColumnAccessibleObject(_owningListView.Columns[Column] as ColumnHeader)); + + return columnHeaders.ToArray(); + } + + internal override bool IsPatternSupported(UIA patternId) + { + if(patternId == UIA.GridItemPatternId || + patternId == UIA.TableItemPatternId) + { + return true; + } + return base.IsPatternSupported(patternId); + } + + private string AutomationId + { + get + { + return string.Format("{0}-{1}", typeof(ListViewItem.ListViewSubItem).Name, GetCurrentIndex()); + } + } + + private IRawElementProviderFragment ParentFragment + { + get + { + var accessibleObject = _owningListView.AccessibilityObject as ListViewAccessibleObject; + return accessibleObject.ItemAccessibleObjects[_owningItem] as IRawElementProviderFragment; + } + } + + private int GetCurrentIndex() + { + return _owningItem.SubItems.IndexOf(_owningSubItem); + } + } + } +} diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ListView.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ListView.cs index aef7d3d4dac..8b04223f5dc 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/ListView.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/ListView.cs @@ -31,7 +31,7 @@ namespace System.Windows.Forms [DefaultProperty(nameof(Items))] [DefaultEvent(nameof(SelectedIndexChanged))] [SRDescription(nameof(SR.DescriptionListView))] - public class ListView : Control + public partial class ListView : Control { //members private const int MASK_HITTESTFLAG = 0x00F7; @@ -9461,43 +9461,5 @@ protected override AccessibleObject CreateAccessibilityInstance() { return new ListViewAccessibleObject(this); } - - internal class ListViewAccessibleObject : ControlAccessibleObject - { - private readonly ListView owner; - - internal ListViewAccessibleObject(ListView owner) : base(owner) - { - this.owner = owner; - } - - internal override bool IsIAccessibleExSupported() - { - if (owner != null) - { - return true; - } - - return base.IsIAccessibleExSupported(); - } - - internal override object GetPropertyValue(UiaCore.UIA propertyID) - { - if (propertyID == UiaCore.UIA.ItemStatusPropertyId) - { - switch (owner.Sorting) - { - case SortOrder.None: - return SR.NotSortedAccessibleStatus; - case SortOrder.Ascending: - return SR.SortedAscendingAccessibleStatus; - case SortOrder.Descending: - return SR.SortedDescendingAccessibleStatus; - } - } - - return base.GetPropertyValue(propertyID); - } - } } } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/PictureBox.PictureBoxAccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/PictureBox.PictureBoxAccessibleObject.cs new file mode 100644 index 00000000000..f71fc74aa7e --- /dev/null +++ b/src/System.Windows.Forms/src/System/Windows/Forms/PictureBox.PictureBoxAccessibleObject.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using static Interop; + +namespace System.Windows.Forms +{ + public partial class PictureBox + { + [ComVisible(true)] + internal class PictureBoxAccessibleObject : ControlAccessibleObject + { + internal PictureBoxAccessibleObject(PictureBox owner) : base(owner) + { + } + + private PictureBox OwningPictureBox => Owner as PictureBox; + + internal override object GetPropertyValue(UiaCore.UIA propertyID) + { + switch (propertyID) + { + case UiaCore.UIA.NamePropertyId: + return Name; + case UiaCore.UIA.ControlTypePropertyId: + return UiaCore.UIA.PaneControlTypeId; + case UiaCore.UIA.AutomationIdPropertyId: + return OwningPictureBox.IsHandleCreated ? OwningPictureBox.Name : String.Empty; + case UiaCore.UIA.IsKeyboardFocusablePropertyId: + return true; + } + + return base.GetPropertyValue(propertyID); + } + } + } +} diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/PictureBox.cs b/src/System.Windows.Forms/src/System/Windows/Forms/PictureBox.cs index 42b0fdd3230..00030f8fc53 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/PictureBox.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/PictureBox.cs @@ -29,7 +29,7 @@ namespace System.Windows.Forms [Docking(DockingBehavior.Ask)] [Designer("System.Windows.Forms.Design.PictureBoxDesigner, " + AssemblyRef.SystemDesign)] [SRDescription(nameof(SR.DescriptionPictureBox))] - public class PictureBox : Control, ISupportInitialize + public partial class PictureBox : Control, ISupportInitialize { /// /// The type of border this control will have. @@ -1200,5 +1200,8 @@ private enum ImageInstallationType ErrorOrInitial, FromUrl } + + protected override AccessibleObject CreateAccessibilityInstance() + => new PictureBoxAccessibleObject(this); } } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/RadioButton.RadioButtonAccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/RadioButton.RadioButtonAccessibleObject.cs new file mode 100644 index 00000000000..3cb99f7fdc2 --- /dev/null +++ b/src/System.Windows.Forms/src/System/Windows/Forms/RadioButton.RadioButtonAccessibleObject.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using static Interop; + +namespace System.Windows.Forms +{ + public partial class RadioButton + { + [ComVisible(true)] + public class RadioButtonAccessibleObject : ControlAccessibleObject + { + public RadioButtonAccessibleObject(RadioButton owner) : base(owner) + { + } + + private RadioButton OwningRadioButton => Owner as RadioButton; + + internal override bool IsPatternSupported(UiaCore.UIA patternId) + { + if (patternId == UiaCore.UIA.SelectionItemPatternId) + { + return true; + } + + return base.IsPatternSupported(patternId); + } + + internal override object GetPropertyValue(UiaCore.UIA propertyID) + { + switch (propertyID) + { + case UiaCore.UIA.NamePropertyId: + return Name; + case UiaCore.UIA.AutomationIdPropertyId: + return OwningRadioButton.IsHandleCreated ? OwningRadioButton.Name : String.Empty; + case UiaCore.UIA.IsSelectionItemPatternAvailablePropertyId: + return IsPatternSupported(UiaCore.UIA.SelectionItemPatternId); + case UiaCore.UIA.ControlTypePropertyId: + return UiaCore.UIA.RadioButtonControlTypeId; + case UiaCore.UIA.IsKeyboardFocusablePropertyId: + // This is necessary for compatibility with MSAA proxy: + // IsKeyboardFocusable = true regardless the control is enabled/disabled. + return true; + } + + return base.GetPropertyValue(propertyID); + } + + public override string DefaultAction + { + get + { + string defaultAction = Owner.AccessibleDefaultActionDescription; + if (defaultAction != null) + { + return defaultAction; + } + + return SR.AccessibleActionCheck; + } + } + + internal override bool IsItemSelected + { + get => OwningRadioButton.Checked; + } + + public override void DoDefaultAction() + { + if (OwningRadioButton.IsHandleCreated) + { + OwningRadioButton.PerformClick(); + } + } + } + } +} diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/RadioButton.cs b/src/System.Windows.Forms/src/System/Windows/Forms/RadioButton.cs index a6249e42091..7b38cf66269 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/RadioButton.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/RadioButton.cs @@ -26,7 +26,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; @@ -602,57 +602,5 @@ public override string ToString() string s = base.ToString(); return s + ", Checked: " + Checked.ToString(); } - - [ComVisible(true)] - 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(); - } - } } } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Splitter.SplitterAccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Splitter.SplitterAccessibleObject.cs new file mode 100644 index 00000000000..43a9b7972d2 --- /dev/null +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Splitter.SplitterAccessibleObject.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using static Interop; + +namespace System.Windows.Forms +{ + public partial class Splitter + { + [ComVisible(true)] + internal class SplitterAccessibleObject : ControlAccessibleObject + { + internal SplitterAccessibleObject(Splitter owner) : base(owner) + { + } + + private Splitter OwningSplitter => Owner as Splitter; + + internal override object GetPropertyValue(UiaCore.UIA propertyID) + { + switch (propertyID) + { + case UiaCore.UIA.NamePropertyId: + return Name; + case UiaCore.UIA.AutomationIdPropertyId: + return OwningSplitter.IsHandleCreated ? OwningSplitter.Name : String.Empty; + case UiaCore.UIA.ControlTypePropertyId: + return UiaCore.UIA.PaneControlTypeId; + case UiaCore.UIA.IsKeyboardFocusablePropertyId: + // This is necessary for compatibility with MSAA proxy: + // IsKeyboardFocusable = true regardless the control is enabled/disabled. + return true; + } + + return base.GetPropertyValue(propertyID); + } + } + } +} diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Splitter.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Splitter.cs index 6238fe1230a..a049d488b7d 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Splitter.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Splitter.cs @@ -24,7 +24,7 @@ namespace System.Windows.Forms [DefaultProperty(nameof(Dock))] [SRDescription(nameof(SR.DescriptionSplitter))] [Designer("System.Windows.Forms.Design.SplitterDesigner, " + AssemblyRef.SystemDesign)] - public class Splitter : Control + public partial class Splitter : Control { private const int DRAW_START = 1; private const int DRAW_MOVE = 2; @@ -971,6 +971,9 @@ public override string ToString() return s + ", MinExtra: " + MinExtra.ToString(CultureInfo.CurrentCulture) + ", MinSize: " + MinSize.ToString(CultureInfo.CurrentCulture); } + protected override AccessibleObject CreateAccessibilityInstance() + => new SplitterAccessibleObject(this); + /// /// Return value holder... /// diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/TextBox.TextBoxAccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/TextBox.TextBoxAccessibleObject.cs new file mode 100644 index 00000000000..8117d7b19c4 --- /dev/null +++ b/src/System.Windows.Forms/src/System/Windows/Forms/TextBox.TextBoxAccessibleObject.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using static System.Windows.Forms.Control; +using static Interop; + +namespace System.Windows.Forms +{ + public partial class TextBox + { + [ComVisible(true)] + internal class TextBoxAccessibleObject : ControlAccessibleObject + { + internal TextBoxAccessibleObject(TextBox owner) : base(owner) + { + } + + public override string Name + { + get + { + string name = base.Name; + if (name == null) + { + name = String.Empty; + } + + // Otherwise just return the default label string, minus any mnemonics + return name; + } + set => base.Name = value; + } + + private TextBox OwningTextBox => Owner as TextBox; + internal override bool IsPatternSupported(UiaCore.UIA patternId) + { + if (patternId == UiaCore.UIA.ValuePatternId) + { + return true; + } + + return base.IsPatternSupported(patternId); + } + + internal override object GetPropertyValue(UiaCore.UIA propertyID) + { + switch (propertyID) + { + case UiaCore.UIA.NamePropertyId: + return Name; + case UiaCore.UIA.AutomationIdPropertyId: + return OwningTextBox.IsHandleCreated ? OwningTextBox.Name : String.Empty; + case UiaCore.UIA.ControlTypePropertyId: + return UiaCore.UIA.EditControlTypeId; + case UiaCore.UIA.IsKeyboardFocusablePropertyId: + // This is necessary for compatibility with MSAA proxy: + // IsKeyboardFocusable = true regardless the control is enabled/disabled. + return true; + } + + return base.GetPropertyValue(propertyID); + } + } + } +} diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/TextBox.cs b/src/System.Windows.Forms/src/System/Windows/Forms/TextBox.cs index e29ab89718e..362122085ca 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/TextBox.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/TextBox.cs @@ -21,7 +21,7 @@ namespace System.Windows.Forms [ComVisible(true)] [Designer("System.Windows.Forms.Design.TextBoxDesigner, " + AssemblyRef.SystemDesign)] [SRDescription(nameof(SR.DescriptionTextBox))] - public class TextBox : TextBoxBase + public partial class TextBox : TextBoxBase { private static readonly object EVENT_TEXTALIGNCHANGED = new object(); @@ -981,5 +981,8 @@ public TestAccessor(TextBox textBox) public bool ShouldRenderPlaceHolderText(in Message m) => _textBox.ShouldRenderPlaceHolderText(m); } + + protected override AccessibleObject CreateAccessibilityInstance() + => new TextBoxAccessibleObject(this); } }