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);
+ }
+ }
+}