diff --git a/src/System.Windows.Forms/src/Resources/SR.resx b/src/System.Windows.Forms/src/Resources/SR.resx index 78ecc5d77e8..b66414853c6 100644 --- a/src/System.Windows.Forms/src/Resources/SR.resx +++ b/src/System.Windows.Forms/src/Resources/SR.resx @@ -6656,4 +6656,7 @@ Stack trace where the illegal operation occurred was: Failed to set Win32 parent window of the Control. + + Error provider + 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 c2e6adbaf85..f48ee5d5d34 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.cs.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.cs.xlf @@ -4594,6 +4594,11 @@ Chcete-li nahradit toto výchozí dialogové okno, nastavte popisovač události Určuje zdroj dat, k nimž mají být vázány chyby. + + Error provider + Error provider + + The error description for a control. Popis chyby pro ovládací prvek. 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 91e176a8caf..32f77bd7ed5 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.de.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.de.xlf @@ -4594,6 +4594,11 @@ Behandeln Sie das DataError-Ereignis, um dieses Standarddialogfeld zu ersetzen.< Zeigt die Datenquelle an, gegen die Fehler gebunden werden sollen. + + Error provider + Error provider + + The error description for a control. Die Fehlerbeschreibung für ein Steuerelement. 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 b3f55e0882b..cc946a55f25 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.es.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.es.xlf @@ -4594,6 +4594,11 @@ Para reemplazar este cuadro de diálogo predeterminado controle el evento DataEr Indica el origen de los datos en los que se deben enlazar errores. + + Error provider + Error provider + + The error description for a control. Descripción del error de un control. 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 edbd41e6a57..af14af72b98 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.fr.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.fr.xlf @@ -4594,6 +4594,11 @@ Pour remplacer cette boîte de dialogue par défaut, traitez l'événement DataE Indique la source de données à laquelle lier les erreurs. + + Error provider + Error provider + + The error description for a control. La description de l'erreur pour un contrôle. 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 d01f189cc28..06396f4710f 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.it.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.it.xlf @@ -4594,6 +4594,11 @@ Per sostituire questa finestra di dialogo predefinita, gestire l'evento DataErro Indica l'origine dati a cui associare gli errori. + + Error provider + Error provider + + The error description for a control. La descrizione errori per un controllo. 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 12d0c4e1460..bcbdca067fc 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.ja.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.ja.xlf @@ -4594,6 +4594,11 @@ To replace this default dialog please handle the DataError event. エラーをバインドする、データのソースを示します。 + + Error provider + Error provider + + The error description for a control. コントロールのエラーの説明です。 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 9e95799e86e..a7fb0a3b7d0 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.ko.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.ko.xlf @@ -4594,6 +4594,11 @@ To replace this default dialog please handle the DataError event. 오류를 바인딩할 데이터 소스를 나타냅니다. + + Error provider + Error provider + + The error description for a control. 컨트롤에 대한 오류 설명입니다. 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 4217e380b78..362a8b3ab2b 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.pl.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.pl.xlf @@ -4594,6 +4594,11 @@ Aby zamienić to domyślne okno dialogowe, obsłuż zdarzenie DataError.Określa źródło danych, z którym zostaną powiązane błędy. + + Error provider + Error provider + + The error description for a control. Opis błędu dla formantu. 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 70d6e49d224..655d7b6527f 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 @@ -4594,6 +4594,11 @@ Para substituir a caixa de diálogo padrão, manipule o evento DataError.Indica a fonte de dados à qual erros devem ser associados. + + Error provider + Error provider + + The error description for a control. A descrição de erro para um controle. 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 0ae416ddf4f..9288d2ced1b 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.ru.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.ru.xlf @@ -4594,6 +4594,11 @@ To replace this default dialog please handle the DataError event. Указывает источник данных для привязки к ним ошибок. + + Error provider + Error provider + + The error description for a control. Описание ошибок для элементов управления. 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 d12838be314..20bcda024bb 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.tr.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.tr.xlf @@ -4594,6 +4594,11 @@ Bu varsayılan iletişim kutusunu değiştirmek için, lütfen DataError olayın Karşılık gelen hataları ilişkilendirmek için veri kaynağını gösterir. + + Error provider + Error provider + + The error description for a control. Denetim için hata açıklaması. 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 717a07c9d00..fdf81c9d74a 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 @@ -4594,6 +4594,11 @@ To replace this default dialog please handle the DataError event. 指示绑定错误的数据源。 + + Error provider + Error provider + + The error description for a control. 控件的错误说明。 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 4acd436fc62..9f7c8f9ab1f 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 @@ -4594,6 +4594,11 @@ To replace this default dialog please handle the DataError event. 表示要繫結錯誤的資料來源。 + + Error provider + Error provider + + The error description for a control. 控制項的錯誤描述。 diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ErrorProvider.ControlItemAccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ErrorProvider.ControlItemAccessibleObject.cs new file mode 100644 index 00000000000..237e6785357 --- /dev/null +++ b/src/System.Windows.Forms/src/System/Windows/Forms/ErrorProvider.ControlItemAccessibleObject.cs @@ -0,0 +1,171 @@ +// 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.Drawing; +using static Interop; + +namespace System.Windows.Forms +{ + partial class ErrorProvider + { + private class ControlItemAccessibleObject : AccessibleObject + { + private readonly ControlItem _controlItem; + private readonly ErrorWindow _window; + private readonly Control _control; + private readonly ErrorProvider _provider; + + public ControlItemAccessibleObject(ControlItem controlItem, ErrorWindow window, Control control, ErrorProvider provider) + { + _controlItem = controlItem; + _window = window; + _control = control; + _provider = provider; + } + + internal override Rectangle BoundingRectangle => Bounds; + + public override Rectangle Bounds => _control.RectangleToScreen(_controlItem.GetIconBounds(_provider.Region.Size)); + + private int ControlItemsCount => _window.ControlItems.Count; + + private int CurrentIndex => _window.ControlItems.IndexOf(_controlItem); + + /// + /// Returns the element in the specified direction. + /// + /// Indicates the direction in which to navigate. + /// Returns the element in the specified direction. + internal override UiaCore.IRawElementProviderFragment FragmentNavigate(UiaCore.NavigateDirection direction) + { + switch (direction) + { + case UiaCore.NavigateDirection.Parent: + return Parent; + case UiaCore.NavigateDirection.NextSibling: + return GetNextSibling(); + case UiaCore.NavigateDirection.PreviousSibling: + return GetPreviousSibling(); + default: + return base.FragmentNavigate(direction); + } + } + + internal override UiaCore.IRawElementProviderFragmentRoot FragmentRoot => Parent; + + internal override int GetChildId() + { + return CurrentIndex + 1; + } + + private AccessibleObject GetNextSibling() + { + int currentIndex = CurrentIndex; + + //Is a last child + if (currentIndex >= ControlItemsCount - 1 || currentIndex < 0) + { + return null; + } + + return _window.ControlItems[currentIndex + 1].AccessibilityObject; + } + + private AccessibleObject GetPreviousSibling() + { + int currentIndex = CurrentIndex; + + //Is a first child + if (currentIndex <= 0 || currentIndex > ControlItemsCount - 1) + { + return null; + } + + return _window.ControlItems[currentIndex - 1].AccessibilityObject; + } + + /// + /// Gets the accessible property value. + /// + /// The accessible property ID. + /// The accessible property value. + internal override object GetPropertyValue(UiaCore.UIA propertyID) + { + switch (propertyID) + { + case UiaCore.UIA.RuntimeIdPropertyId: + return RuntimeId; + case UiaCore.UIA.ControlTypePropertyId: + return UiaCore.UIA.ImageControlTypeId; + case UiaCore.UIA.BoundingRectanglePropertyId: + return BoundingRectangle; + case UiaCore.UIA.NamePropertyId: + return Name; + case UiaCore.UIA.HelpTextPropertyId: + return Help; + case UiaCore.UIA.LegacyIAccessibleStatePropertyId: + return State; + case UiaCore.UIA.NativeWindowHandlePropertyId: + return _window.Handle; + case UiaCore.UIA.IsLegacyIAccessiblePatternAvailablePropertyId: + return IsPatternSupported(UiaCore.UIA.LegacyIAccessiblePatternId); + default: + return base.GetPropertyValue(propertyID); + } + } + + internal override bool IsIAccessibleExSupported() + { + if (_controlItem != null) + { + return true; + } + + return base.IsIAccessibleExSupported(); + } + + internal override bool IsPatternSupported(UiaCore.UIA patternId) + { + if (patternId == UiaCore.UIA.LegacyIAccessiblePatternId) + { + return true; + } + + return base.IsPatternSupported(patternId); + } + + internal override bool IsReadOnly => true; + + public override string Name + { + get => string.IsNullOrEmpty(base.Name) ? _controlItem.Error : base.Name; + set => base.Name = value; + } + + public override AccessibleObject Parent => _window.AccessibilityObject; + + public override AccessibleRole Role => AccessibleRole.Alert; + + /// + /// Gets the runtime ID. + /// + internal override int[] RuntimeId + { + get + { + var runtimeId = new int[4]; + + runtimeId[0] = _window.AccessibilityObject.RuntimeId[0]; + runtimeId[1] = _window.AccessibilityObject.RuntimeId[1]; + runtimeId[2] = _window.AccessibilityObject.RuntimeId[2]; + runtimeId[3] = _controlItem.GetHashCode(); + + return runtimeId; + } + } + + public override AccessibleStates State => AccessibleStates.HasPopup | AccessibleStates.ReadOnly; + } + } +} diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ErrorProvider.ErrorWindowAccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ErrorProvider.ErrorWindowAccessibleObject.cs new file mode 100644 index 00000000000..2d29efe00fb --- /dev/null +++ b/src/System.Windows.Forms/src/System/Windows/Forms/ErrorProvider.ErrorWindowAccessibleObject.cs @@ -0,0 +1,177 @@ +// 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 +{ + partial class ErrorProvider + { + private class ErrorWindowAccessibleObject : AccessibleObject + { + private readonly ErrorWindow _owner; + + public ErrorWindowAccessibleObject(ErrorWindow owner) + { + _owner = owner; + } + + /// + /// Return the child object at the given screen coordinates. + /// + /// X coordinate. + /// Y coordinate. + /// The accessible object of corresponding element in the provided coordinates. + internal override UiaCore.IRawElementProviderFragment ElementProviderFromPoint(double x, double y) + { + AccessibleObject element = HitTest((int)x, (int)y); + + if (element != null) + { + return element; + } + + return base.ElementProviderFromPoint(x, y); + } + + /// + /// Returns the element in the specified direction. + /// + /// Indicates the direction in which to navigate. + /// Returns the element in the specified direction. + internal override UiaCore.IRawElementProviderFragment FragmentNavigate(UiaCore.NavigateDirection direction) + { + switch (direction) + { + case UiaCore.NavigateDirection.FirstChild: + return GetChild(0); + case UiaCore.NavigateDirection.LastChild: + return GetChild(GetChildCount() - 1); + } + + return base.FragmentNavigate(direction); + } + + internal override UiaCore.IRawElementProviderFragmentRoot FragmentRoot => this; + + public override AccessibleObject GetChild(int index) + { + if (index >= 0 && index <= GetChildCount() - 1) + { + return _owner.ControlItems[index].AccessibilityObject; + } + + return base.GetChild(index); + } + + public override int GetChildCount() => _owner.ControlItems.Count; + + /// + /// Gets the accessible property value. + /// + /// The accessible property ID. + /// The accessible property value. + internal override object GetPropertyValue(UiaCore.UIA propertyID) + { + switch (propertyID) + { + case UiaCore.UIA.RuntimeIdPropertyId: + return RuntimeId; + case UiaCore.UIA.ControlTypePropertyId: + return UiaCore.UIA.GroupControlTypeId; + case UiaCore.UIA.NamePropertyId: + return Name; + case UiaCore.UIA.HelpTextPropertyId: + return Help; + case UiaCore.UIA.LegacyIAccessibleStatePropertyId: + return State; + case UiaCore.UIA.NativeWindowHandlePropertyId: + return _owner.Handle; + case UiaCore.UIA.IsLegacyIAccessiblePatternAvailablePropertyId: + return IsPatternSupported(UiaCore.UIA.LegacyIAccessiblePatternId); + default: + return base.GetPropertyValue(propertyID); + } + } + + public override AccessibleObject HitTest(int x, int y) + { + foreach (ControlItem control in _owner.ControlItems) + { + if (control.AccessibilityObject.Bounds.Contains(x, y)) + { + return control.AccessibilityObject; + } + } + + return null; + } + + internal override UiaCore.IRawElementProviderSimple HostRawElementProvider + { + get + { + UiaCore.UiaHostProviderFromHwnd(new HandleRef(this, _owner.Handle), out UiaCore.IRawElementProviderSimple provider); + return provider; + } + } + + internal override bool IsIAccessibleExSupported() + { + if (_owner != null) + { + return true; + } + + return base.IsIAccessibleExSupported(); + } + + internal override bool IsPatternSupported(UiaCore.UIA patternId) + { + if (patternId == UiaCore.UIA.LegacyIAccessiblePatternId) + { + return true; + } + + return base.IsPatternSupported(patternId); + } + + internal override bool IsReadOnly => true; + + public override string Name + { + get => string.IsNullOrEmpty(base.Name) ? SR.ErrorProviderDefaultAccessibleName : base.Name; + set => base.Name = value; + } + + public override AccessibleRole Role => AccessibleRole.Grouping; + + internal override int[] RuntimeId + { + get + { + if (_owner == null) + { + return base.RuntimeId; + } + + // we need to provide a unique ID + // others are implementing this in the same manner + // first item is static - 0x2a (RuntimeIDFirstItem) + // second item can be anything, but here it is a hash + + var runtimeId = new int[3]; + runtimeId[0] = RuntimeIDFirstItem; + runtimeId[1] = (int)(long)_owner.Handle; + runtimeId[2] = _owner.GetHashCode(); + + return runtimeId; + } + } + + public override AccessibleStates State => AccessibleStates.ReadOnly; + } + } +} diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ErrorProvider.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ErrorProvider.cs index d4b2d692f39..3c85650cecb 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/ErrorProvider.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/ErrorProvider.cs @@ -1,8 +1,9 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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.Collections; +using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.Design; using System.Diagnostics; @@ -26,7 +27,7 @@ namespace System.Windows.Forms [ToolboxItemFilter("System.Windows.Forms")] [ComplexBindingProperties(nameof(DataSource), nameof(DataMember))] [SRDescription(nameof(SR.DescriptionErrorProvider))] - public class ErrorProvider : Component, IExtenderProvider, ISupportInitialize + public partial class ErrorProvider : Component, IExtenderProvider, ISupportInitialize { private readonly Hashtable _items = new Hashtable(); private readonly Hashtable _windows = new Hashtable(); @@ -816,7 +817,9 @@ public void SetIconPadding(Control control, int padding) /// internal class ErrorWindow : NativeWindow { - private readonly ArrayList items = new ArrayList(); + private static readonly int s_accessibilityProperty = PropertyStore.CreateKey(); + + private readonly List _items = new List(); private readonly Control _parent; private readonly ErrorProvider _provider; private Rectangle _windowBounds; @@ -835,6 +838,26 @@ public ErrorWindow(ErrorProvider provider, Control parent) { _provider = provider; _parent = parent; + Properties = new PropertyStore(); + } + + /// + /// The Accessibility Object for this ErrorProvider + /// + internal AccessibleObject AccessibilityObject + { + get + { + AccessibleObject accessibleObject = (AccessibleObject)Properties.GetObject(s_accessibilityProperty); + + if (accessibleObject == null) + { + accessibleObject = CreateAccessibilityInstance(); + Properties.SetObject(s_accessibilityProperty, accessibleObject); + } + + return accessibleObject; + } } /// @@ -842,7 +865,7 @@ public ErrorWindow(ErrorProvider provider, Control parent) /// public void Add(ControlItem item) { - items.Add(item); + _items.Add(item); if (!EnsureCreated()) { return; @@ -854,6 +877,17 @@ public void Add(ControlItem item) Update(timerCaused: false); } + internal List ControlItems => _items; + + /// + /// Constructs the new instance of the accessibility object for this ErrorProvider. Subclasses + /// should not call base.CreateAccessibilityObject. + /// + private AccessibleObject CreateAccessibilityInstance() + { + return new ErrorWindowAccessibleObject(this); + } + /// /// Called to get rid of any resources the Object may have. /// @@ -1000,9 +1034,9 @@ private void OnPaint(ref Message m) try { - for (int i = 0; i < items.Count; i++) + for (int i = 0; i < _items.Count; i++) { - ControlItem item = (ControlItem)items[i]; + ControlItem item = _items[i]; Rectangle bounds = item.GetIconBounds(_provider.Region.Size); User32.DrawIconEx( _mirrordc, @@ -1034,9 +1068,9 @@ protected override void OnThreadException(Exception e) private void OnTimer(object sender, EventArgs e) { int blinkPhase = 0; - for (int i = 0; i < items.Count; i++) + for (int i = 0; i < _items.Count; i++) { - blinkPhase += ((ControlItem)items[i]).BlinkPhase; + blinkPhase += _items[i].BlinkPhase; } if (blinkPhase == 0 && _provider.BlinkStyle != ErrorBlinkStyle.AlwaysBlink) { @@ -1048,18 +1082,18 @@ private void OnTimer(object sender, EventArgs e) private void OnToolTipVisibilityChanging(IntPtr id, bool toolTipShown) { - for (int i = 0; i < items.Count; i++) + for (int i = 0; i < _items.Count; i++) { - if (((ControlItem)items[i]).Id == id) + if (_items[i].Id == id) { - ((ControlItem)items[i]).ToolTipShown = toolTipShown; + _items[i].ToolTipShown = toolTipShown; } } #if DEBUG int shownTooltips = 0; - for (int j = 0; j < items.Count; j++) + for (int j = 0; j < _items.Count; j++) { - if (((ControlItem)items[j]).ToolTipShown) + if (_items[j].ToolTipShown) { shownTooltips++; } @@ -1068,12 +1102,19 @@ private void OnToolTipVisibilityChanging(IntPtr id, bool toolTipShown) #endif } + /// + /// Retrieves our internal property storage object. If you have a property + /// whose value is not always set, you should store it in here to save + /// space. + /// + internal PropertyStore Properties { get; } + /// /// This is called when a control no longer needs to display an error icon. /// public void Remove(ControlItem item) { - items.Remove(item); + _items.Remove(item); if (_tipWindow != null) { @@ -1081,7 +1122,7 @@ public void Remove(ControlItem item) info.SendMessage(_tipWindow, User32.WindowMessage.TTM_DELTOOLW); } - if (items.Count == 0) + if (_items.Count == 0) { EnsureDestroyed(); } @@ -1124,9 +1165,9 @@ public unsafe void Update(bool timerCaused) IconRegion iconRegion = _provider.Region; Size size = iconRegion.Size; _windowBounds = Rectangle.Empty; - for (int i = 0; i < items.Count; i++) + for (int i = 0; i < _items.Count; i++) { - ControlItem item = (ControlItem)items[i]; + ControlItem item = _items[i]; Rectangle iconBounds = item.GetIconBounds(size); if (_windowBounds.IsEmpty) { @@ -1142,9 +1183,9 @@ public unsafe void Update(bool timerCaused) IntPtr windowRegionHandle = IntPtr.Zero; try { - for (int i = 0; i < items.Count; i++) + for (int i = 0; i < _items.Count; i++) { - ControlItem item = (ControlItem)items[i]; + ControlItem item = _items[i]; Rectangle iconBounds = item.GetIconBounds(size); iconBounds.X -= _windowBounds.X; iconBounds.Y -= _windowBounds.Y; @@ -1250,6 +1291,31 @@ public unsafe void Update(bool timerCaused) User32.InvalidateRect(new HandleRef(this, Handle), null, BOOL.FALSE); } + /// + /// Handles the WM_GETOBJECT message. Used for accessibility. + /// + private void WmGetObject(ref Message m) + { + Debug.WriteLineIf(CompModSwitches.MSAA.TraceInfo, "In WmGetObject, this = " + GetType().FullName + ", lParam = " + m.LParam.ToString()); + + if (m.Msg == WindowMessages.WM_GETOBJECT && m.LParam == (IntPtr)NativeMethods.UiaRootObjectId) + { + // If the requested object identifier is UiaRootObjectId, + // we should return an UI Automation provider using the UiaReturnRawElementProvider function. + InternalAccessibleObject intAccessibleObject = new InternalAccessibleObject(AccessibilityObject); + m.Result = UiaCore.UiaReturnRawElementProvider( + new HandleRef(this, Handle), + m.WParam, + m.LParam, + intAccessibleObject); + + return; + } + + // some accessible object requested that we don't care about, so do default message processing + DefWndProc(ref m); + } + /// /// Called when the error window gets a windows message. /// @@ -1257,6 +1323,9 @@ protected unsafe override void WndProc(ref Message m) { switch (m.Msg) { + case WindowMessages.WM_GETOBJECT: + WmGetObject(ref m); + break; case WindowMessages.WM_NOTIFY: User32.NMHDR* nmhdr = (User32.NMHDR*)m.LParam; if (nmhdr->code == NativeMethods.TTN_SHOW || nmhdr->code == NativeMethods.TTN_POP) @@ -1282,6 +1351,8 @@ protected unsafe override void WndProc(ref Message m) /// internal class ControlItem { + private static readonly int s_accessibilityProperty = PropertyStore.CreateKey(); + private string _error; private readonly Control _control; private ErrorWindow _window; @@ -1307,6 +1378,35 @@ public ControlItem(ErrorProvider provider, Control control, IntPtr id) _control.SizeChanged += new EventHandler(OnBoundsChanged); _control.VisibleChanged += new EventHandler(OnParentVisibleChanged); _control.ParentChanged += new EventHandler(OnParentVisibleChanged); + Properties = new PropertyStore(); + } + + /// + /// The Accessibility Object for this ErrorProvider + /// + internal AccessibleObject AccessibilityObject + { + get + { + AccessibleObject accessibleObject = (AccessibleObject)Properties.GetObject(s_accessibilityProperty); + + if (accessibleObject == null) + { + accessibleObject = CreateAccessibilityInstance(); + Properties.SetObject(s_accessibilityProperty, accessibleObject); + } + + return accessibleObject; + } + } + + /// + /// Constructs the new instance of the accessibility object for this ErrorProvider. Subclasses + /// should not call base.CreateAccessibilityObject. + /// + private AccessibleObject CreateAccessibilityInstance() + { + return new ControlItemAccessibleObject(this, _window, _control.ParentInternal, _provider); } public void Dispose() @@ -1567,6 +1667,13 @@ void OnParentVisibleChanged(object sender, EventArgs e) AddToWindow(); } + /// + /// Retrieves our internal property storage object. If you have a property + /// whose value is not always set, you should store it in here to save + /// space. + /// + private PropertyStore Properties { get; } + /// /// This is called when the control's handle is created. /// diff --git a/src/System.Windows.Forms/tests/UnitTests/AccessibleObjects/ErrorProviderAccessibleObjectTests.cs b/src/System.Windows.Forms/tests/UnitTests/AccessibleObjects/ErrorProviderAccessibleObjectTests.cs new file mode 100644 index 00000000000..0efada86402 --- /dev/null +++ b/src/System.Windows.Forms/tests/UnitTests/AccessibleObjects/ErrorProviderAccessibleObjectTests.cs @@ -0,0 +1,161 @@ +// 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; + +namespace System.Windows.Forms.Tests.AccessibleObjects +{ + public class ErrorProviderAccessibleObjectTests + { + private readonly Form _form; + private readonly Control _control1; + private readonly Control _control2; + private readonly ErrorProvider _errorProvider; + private readonly ErrorProvider.ErrorWindow _errorWindow; + private readonly ErrorProvider.ControlItem _controlItem1; + private readonly ErrorProvider.ControlItem _controlItem2; + private readonly string _errorText1; + private readonly string _errorText2; + + public ErrorProviderAccessibleObjectTests() + { + _form = new Form(); + _form.CreateControl(); + _form.Visible = true; + + _control1 = new Control(); + _control1.CreateControl(); + _control1.Visible = true; + + _control2 = new Control(); + _control2.CreateControl(); + _control2.Visible = true; + + _form.Controls.Add(_control1); + _form.Controls.Add(_control2); + + _errorText1 = "Test text 1"; + _errorText2 = "Test text 2"; + + _errorProvider = new ErrorProvider(); + _errorProvider.SetError(_control1, _errorText1); + _errorProvider.SetError(_control2, _errorText2); + + _errorWindow = _errorProvider.EnsureErrorWindow(_form); + _controlItem1 = _errorWindow.ControlItems.Count > 0 ? _errorWindow.ControlItems[0] : null; + _controlItem2 = _errorWindow.ControlItems.Count > 0 ? _errorWindow.ControlItems[1] : null; + } + + [WinFormsFact] + public void ErrorProviderAccessibleObject_Ctor_Default() + { + Assert.NotNull(_errorWindow); + AccessibleObject windowAccessibleObject = _errorWindow.AccessibilityObject; + Assert.NotNull(windowAccessibleObject); + + Assert.NotNull(_controlItem1); + AccessibleObject controlItemAccessibleObject1 = _controlItem1.AccessibilityObject; + Assert.NotNull(controlItemAccessibleObject1); + + Assert.NotNull(_controlItem2); + AccessibleObject controlItemAccessibleObject2 = _controlItem2.AccessibilityObject; + Assert.NotNull(controlItemAccessibleObject2); + } + + [WinFormsFact] + public void ErrorProvider_ControlItemAccessibleObject_CorrectControlType() + { + AccessibleObject controlItemAccessibleObject = _controlItem1.AccessibilityObject; + object actual = controlItemAccessibleObject?.GetPropertyValue(UiaCore.UIA.ControlTypePropertyId); + object expected = UiaCore.UIA.ImageControlTypeId; + Assert.Equal(expected, actual); + } + + [WinFormsFact] + public void ErrorProvider_ErrorWindowAccessibleObject_CorrectControlType() + { + AccessibleObject errorWindowAccessibleObject = _errorWindow.AccessibilityObject; + object actual = errorWindowAccessibleObject?.GetPropertyValue(UiaCore.UIA.ControlTypePropertyId); + object expected = UiaCore.UIA.GroupControlTypeId; + Assert.Equal(expected, actual); + } + + [WinFormsFact] + public void ErrorProvider_CorrectErrorValue() + { + // Check string for _control1 + string actual = _errorProvider.GetError(_control1); + string expected = _errorText1; + Assert.Equal(expected, actual); + + actual = _controlItem1.Error; + Assert.Equal(expected, actual); + + // Check string for _control2 + actual = _errorProvider.GetError(_control2); + expected = _errorText2; + Assert.Equal(expected, actual); + + actual = _controlItem2.Error; + Assert.Equal(expected, actual); + + } + + [WinFormsFact] + public void ErrorProvider_CorrectAccessibilityTree() + { + AccessibleObject errorWindowAccessibilityObject = _errorWindow.AccessibilityObject; + AccessibleObject controlItem1_AccessibilityObject = _controlItem1.AccessibilityObject; + AccessibleObject controlItem2_AccessibilityObject = _controlItem2.AccessibilityObject; + + int childCound = errorWindowAccessibilityObject.GetChildCount(); + int expectedCount = 2; + Assert.Equal(expectedCount, childCound); + + AccessibleObject actualAccessibilityObject = errorWindowAccessibilityObject.GetChild(0); + Assert.Equal(controlItem1_AccessibilityObject, actualAccessibilityObject); + + actualAccessibilityObject = errorWindowAccessibilityObject.GetChild(1); + Assert.Equal(controlItem2_AccessibilityObject, actualAccessibilityObject); + + actualAccessibilityObject = controlItem1_AccessibilityObject.Parent; + Assert.Equal(errorWindowAccessibilityObject, actualAccessibilityObject); + + actualAccessibilityObject = controlItem2_AccessibilityObject.Parent; + Assert.Equal(errorWindowAccessibilityObject, actualAccessibilityObject); + + actualAccessibilityObject = (AccessibleObject)controlItem1_AccessibilityObject.FragmentNavigate(UiaCore.NavigateDirection.NextSibling); + Assert.Equal(controlItem2_AccessibilityObject, actualAccessibilityObject); + + actualAccessibilityObject = (AccessibleObject)controlItem1_AccessibilityObject.FragmentNavigate(UiaCore.NavigateDirection.PreviousSibling); + Assert.Null(actualAccessibilityObject); + + actualAccessibilityObject = (AccessibleObject)controlItem2_AccessibilityObject.FragmentNavigate(UiaCore.NavigateDirection.PreviousSibling); + Assert.Equal(controlItem1_AccessibilityObject, actualAccessibilityObject); + + actualAccessibilityObject = (AccessibleObject)controlItem2_AccessibilityObject.FragmentNavigate(UiaCore.NavigateDirection.NextSibling); + Assert.Null(actualAccessibilityObject); + } + + [WinFormsFact] + public void ErrorProvider_NameDoesntEqualControlTypeOrChildName() + { + // Mas requires us to have no same AccessibleName and LocalizedControlType or child AccessibleName. + // So we need to check if these properties are not equal. + // In this specific case, we can't have some positive Assert. + // ToLower method used to be case insensitive. + + string errorWindowControlType = "group"; + string actualWindowAccessibleName = _errorWindow.AccessibilityObject.Name.ToLower(); + Assert.NotEqual(errorWindowControlType, actualWindowAccessibleName); + + string controlItemControlType = "image"; + string actualItemAccessibleName = _controlItem1.AccessibilityObject.Name.ToLower(); + Assert.NotEqual(controlItemControlType, actualItemAccessibleName); + + Assert.NotEqual(actualWindowAccessibleName, actualItemAccessibleName); + } + } +}