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..205b65c9d24
--- /dev/null
+++ b/src/System.Windows.Forms.Primitives/src/Interop/UiaCore/Interop.IMultipleViewProvider.cs
@@ -0,0 +1,25 @@
+// 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 b9c99e75383..a2bdcb28a86 100644
--- a/src/System.Windows.Forms/src/Resources/SR.resx
+++ b/src/System.Windows.Forms/src/Resources/SR.resx
@@ -6722,4 +6722,7 @@ Stack trace where the illegal operation occurred was:
TextFormatFlags.ModifyString is not allowed.
+
+ 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 f7af6dc7f11..e7623cbe0d6 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
+
+
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 ac2dfd022b2..02436bc4774 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
+
+
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 1db8c39e1c2..1083ca764e2 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
+
+
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 06d53011a76..531bb201f1b 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
+
+
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 9044eeb3b6e..707b63014c5 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
+
+
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 a8bcad455df..2295425ea39 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
+
+
展開
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 61e46fbd902..b61dc85f2a7 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
+
+
확장
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 c9169bd11ff..fdd598310d5 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
+
+
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 bd6716e7969..e90b3cb3951 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
+
+
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 0db028371ca..3f6925868c4 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
+
+
Развертывание
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 0c61115532f..e8d3137871e 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
+
+
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 dcc9c56ade1..c2d48c6e0cd 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
+
+
展开
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 db2e4132abc..c3b0921f0b9 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
+
+
展開
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 6c11dd851f5..6ddf229ee3f 100644
--- a/src/System.Windows.Forms/src/System/Windows/Forms/AccessibleObject.cs
+++ b/src/System.Windows.Forms/src/System/Windows/Forms/AccessibleObject.cs
@@ -42,7 +42,8 @@ public partial class AccessibleObject :
UiaCore.ISelectionProvider,
UiaCore.ISelectionItemProvider,
UiaCore.IRawElementProviderHwndOverride,
- UiaCore.IScrollItemProvider
+ UiaCore.IScrollItemProvider,
+ UiaCore.IMultipleViewProvider
{
///
/// Specifies the interface used by this .
@@ -482,6 +483,16 @@ internal virtual void SetValue(string? newValue)
internal virtual UiaCore.IRawElementProviderSimple? GetOverrideProviderForHwnd(IntPtr hwnd) => null;
+ internal virtual int GetMultiViewProviderCurrentView() => 0;
+
+ internal virtual int[]? GetMultiViewProviderSupportedViews() => Array.Empty();
+
+ internal virtual string GetMultiViewProviderViewName(int viewId) => string.Empty;
+
+ internal virtual void SetMultiViewProviderCurrentView(int viewId)
+ {
+ }
+
internal virtual void SetValue(double newValue)
{
}
@@ -1782,6 +1793,14 @@ MemberInfo[] IReflect.GetMembers(BindingFlags bindingAttr)
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;
diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ColumnHeader.ListViewColumnHeaderAccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ColumnHeader.ListViewColumnHeaderAccessibleObject.cs
new file mode 100644
index 00000000000..c1f8b472c55
--- /dev/null
+++ b/src/System.Windows.Forms/src/System/Windows/Forms/ColumnHeader.ListViewColumnHeaderAccessibleObject.cs
@@ -0,0 +1,29 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using static Interop.UiaCore;
+
+namespace System.Windows.Forms
+{
+ public partial class ColumnHeader
+ {
+ internal class ListViewColumnHeaderAccessibleObject : AccessibleObject
+ {
+ private readonly ColumnHeader _owningColumnHeader;
+
+ public ListViewColumnHeaderAccessibleObject(ColumnHeader columnHeader)
+ {
+ _owningColumnHeader = columnHeader ?? throw new ArgumentNullException(nameof(columnHeader));
+ }
+
+ internal override object? GetPropertyValue(UIA propertyID)
+ => propertyID switch
+ {
+ UIA.ControlTypePropertyId => UIA.HeaderItemControlTypeId,
+ UIA.NamePropertyId => _owningColumnHeader.Text,
+ _ => base.GetPropertyValue(propertyID)
+ };
+ }
+ }
+}
diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ColumnHeader.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ColumnHeader.cs
index 4e58843fd68..b4a34cfa1d3 100644
--- a/src/System.Windows.Forms/src/System/Windows/Forms/ColumnHeader.cs
+++ b/src/System.Windows.Forms/src/System/Windows/Forms/ColumnHeader.cs
@@ -19,7 +19,7 @@ namespace System.Windows.Forms
[DesignTimeVisible(false)]
[DefaultProperty(nameof(Text))]
[TypeConverter(typeof(ColumnHeaderConverter))]
- public class ColumnHeader : Component, ICloneable
+ public partial class ColumnHeader : Component, ICloneable
{
// disable csharp compiler warning #0414: field assigned unused value
#pragma warning disable 0414
@@ -32,6 +32,7 @@ public class ColumnHeader : Component, ICloneable
// Use TextAlign property instead of this member variable, always
private HorizontalAlignment _textAlign = HorizontalAlignment.Left;
private bool _textAlignInitialized;
+ private AccessibleObject _accessibilityObject;
private readonly ColumnHeaderImageListIndexer _imageIndexer;
// We need to send some messages to ListView when it gets initialized.
@@ -76,6 +77,19 @@ public ColumnHeader(string imageKey) : this()
ImageKey = imageKey;
}
+ internal AccessibleObject AccessibilityObject
+ {
+ get
+ {
+ if (_accessibilityObject is null)
+ {
+ _accessibilityObject = new ListViewColumnHeaderAccessibleObject(this);
+ }
+
+ return _accessibilityObject;
+ }
+ }
+
internal int ActualImageIndex_Internal
{
get
diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/InternalAccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/InternalAccessibleObject.cs
index d40c6e8a7fb..dedf92aba1b 100644
--- a/src/System.Windows.Forms/src/System/Windows/Forms/InternalAccessibleObject.cs
+++ b/src/System.Windows.Forms/src/System/Windows/Forms/InternalAccessibleObject.cs
@@ -39,7 +39,8 @@ internal sealed class InternalAccessibleObject :
ISelectionProvider,
ISelectionItemProvider,
IScrollItemProvider,
- IRawElementProviderHwndOverride
+ IRawElementProviderHwndOverride,
+ IMultipleViewProvider
{
private IAccessible publicIAccessible; // AccessibleObject as IAccessible
private readonly Oleaut32.IEnumVariant publicIEnumVariant; // AccessibleObject as Oleaut32.IEnumVariant
@@ -67,6 +68,7 @@ internal sealed class InternalAccessibleObject :
private readonly ISelectionItemProvider publicISelectionItemProvider; // AccessibleObject as ISelectionItemProvider
private readonly IScrollItemProvider publicIScrollItemProvider; // AccessibleObject as IScrollItemProvider
private readonly IRawElementProviderHwndOverride publicIRawElementProviderHwndOverride; // AccessibleObject as IRawElementProviderHwndOverride
+ private readonly IMultipleViewProvider publicIMultiViewProvider; // AccessibleObject as IMultipleViewProvider
///
/// Create a new wrapper.
@@ -97,6 +99,7 @@ internal InternalAccessibleObject(AccessibleObject accessibleImplemention)
publicISelectionItemProvider = (ISelectionItemProvider)accessibleImplemention;
publicIScrollItemProvider = (IScrollItemProvider)accessibleImplemention;
publicIRawElementProviderHwndOverride = (IRawElementProviderHwndOverride)accessibleImplemention;
+ publicIMultiViewProvider = (IMultipleViewProvider)accessibleImplemention;
// Note: Deliberately not holding onto AccessibleObject to enforce all access through the interfaces
}
@@ -289,70 +292,30 @@ ProviderOptions IRawElementProviderSimple.ProviderOptions
object? IRawElementProviderSimple.GetPatternProvider(UIA patternId)
{
object? obj = publicIRawElementProviderSimple.GetPatternProvider(patternId);
- if (obj != null)
- {
- // we always want to return the internal accessible object
- if (patternId == UIA.ExpandCollapsePatternId)
- {
- return (IExpandCollapseProvider)this;
- }
- else if (patternId == UIA.ValuePatternId)
- {
- return (IValueProvider)this;
- }
- else if (patternId == UIA.RangeValuePatternId)
- {
- return (IRangeValueProvider)this;
- }
- else if (patternId == UIA.TogglePatternId)
- {
- return (IToggleProvider)this;
- }
- else if (patternId == UIA.TablePatternId)
- {
- return (ITableProvider)this;
- }
- else if (patternId == UIA.TableItemPatternId)
- {
- return (ITableItemProvider)this;
- }
- else if (patternId == UIA.GridPatternId)
- {
- return (IGridProvider)this;
- }
- else if (patternId == UIA.GridItemPatternId)
- {
- return (IGridItemProvider)this;
- }
- else if (patternId == UIA.InvokePatternId)
- {
- return (IInvokeProvider)this;
- }
- else if (patternId == UIA.LegacyIAccessiblePatternId)
- {
- return (ILegacyIAccessibleProvider)this;
- }
- else if (patternId == UIA.SelectionPatternId)
- {
- return (ISelectionProvider)this;
- }
- else if (patternId == UIA.SelectionItemPatternId)
- {
- return (ISelectionItemProvider)this;
- }
- else if (patternId == UIA.ScrollItemPatternId)
- {
- return (IScrollItemProvider)this;
- }
- else
- {
- return null;
- }
- }
- else
+ if (obj is null)
{
return null;
}
+
+ // we always want to return the internal accessible object
+ return patternId switch
+ {
+ UIA.ExpandCollapsePatternId => (IExpandCollapseProvider)this,
+ UIA.ValuePatternId => (IValueProvider)this,
+ UIA.RangeValuePatternId => (IRangeValueProvider)this,
+ UIA.TogglePatternId => (IToggleProvider)this,
+ UIA.TablePatternId => (ITableProvider)this,
+ UIA.TableItemPatternId => (ITableItemProvider)this,
+ UIA.GridPatternId => (IGridProvider)this,
+ UIA.GridItemPatternId => (IGridItemProvider)this,
+ UIA.InvokePatternId => (IInvokeProvider)this,
+ UIA.LegacyIAccessiblePatternId => (ILegacyIAccessibleProvider)this,
+ UIA.SelectionPatternId => (ISelectionProvider)this,
+ UIA.SelectionItemPatternId => (ISelectionItemProvider)this,
+ UIA.ScrollItemPatternId => (IScrollItemProvider)this,
+ UIA.MultipleViewPatternId => (IMultipleViewProvider)this,
+ _ => null
+ };
}
object? IRawElementProviderSimple.GetPropertyValue(UIA propertyID)
@@ -537,5 +500,17 @@ ExpandCollapseState IExpandCollapseProvider.ExpandCollapseState
/// Return the provider for the specified component, or null if the component is not being overridden.
IRawElementProviderSimple? IRawElementProviderHwndOverride.GetOverrideProviderForHwnd(IntPtr hwnd)
=> publicIRawElementProviderHwndOverride.GetOverrideProviderForHwnd(hwnd);
+
+ int IMultipleViewProvider.CurrentView
+ => publicIMultiViewProvider.CurrentView;
+
+ int[]? IMultipleViewProvider.GetSupportedViews()
+ => publicIMultiViewProvider.GetSupportedViews();
+
+ string? IMultipleViewProvider.GetViewName(int viewId)
+ => publicIMultiViewProvider.GetViewName(viewId);
+
+ void IMultipleViewProvider.SetCurrentView(int viewId)
+ => publicIMultiViewProvider.SetCurrentView(viewId);
}
}
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..88ac6937c83
--- /dev/null
+++ b/src/System.Windows.Forms/src/System/Windows/Forms/ListView.ListViewAccessibleObject.cs
@@ -0,0 +1,340 @@
+// 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.Generic;
+using System.Drawing;
+using static Interop;
+
+namespace System.Windows.Forms
+{
+ public partial class ListView
+ {
+ internal class ListViewAccessibleObject : ControlAccessibleObject
+ {
+ private readonly ListView _owningListView;
+ private static readonly int[] s_enumViewValues = (int[])Enum.GetValues(typeof(View));
+
+ internal ListViewAccessibleObject(ListView owningListView) : base(owningListView)
+ {
+ _owningListView = owningListView;
+ }
+
+ internal override bool CanSelectMultiple
+ => _owningListView.IsHandleCreated;
+
+ internal override int ColumnCount
+ => _owningListView.Columns.Count;
+
+ private bool OwnerHasDefaultGroup
+ {
+ get
+ {
+ if (!_owningListView.IsHandleCreated || !_owningListView.ShowGroups)
+ {
+ return false;
+ }
+
+ foreach (ListViewItem? item in _owningListView.Items)
+ {
+ // If there are groups in the ListView, then the items which do not belong
+ // to any of the group and have null as the item.Group value, so these items
+ // are put into the default group and thereby the ListView itself starts
+ // containing Default group.
+ if (item != null && item.Group is null)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ private bool OwnerHasGroups
+ => _owningListView.IsHandleCreated && _owningListView.Groups.Count > 0;
+
+ internal override int RowCount
+ => _owningListView.Items.Count;
+
+ internal override UiaCore.RowOrColumnMajor RowOrColumnMajor
+ => UiaCore.RowOrColumnMajor.RowMajor;
+
+ internal override int[]? RuntimeId
+ {
+ get
+ {
+ if (!_owningListView.IsHandleCreated)
+ {
+ return base.RuntimeId;
+ }
+
+ var runtimeId = new int[2];
+ runtimeId[0] = RuntimeIDFirstItem;
+ runtimeId[1] = PARAM.ToInt(_owningListView.Handle);
+ return runtimeId;
+ }
+ }
+
+ internal override UiaCore.IRawElementProviderFragment ElementProviderFromPoint(double x, double y)
+ {
+ AccessibleObject? element = HitTest((int)x, (int)y);
+
+ return element ?? base.ElementProviderFromPoint(x, y);
+ }
+
+ internal override UiaCore.IRawElementProviderFragment? FragmentNavigate(UiaCore.NavigateDirection direction)
+ {
+ if (!_owningListView.IsHandleCreated)
+ {
+ return null;
+ }
+
+ int childCount = GetChildCount();
+ if (childCount == 0)
+ {
+ return null;
+ }
+
+ return direction switch
+ {
+ UiaCore.NavigateDirection.FirstChild => GetChild(0),
+ UiaCore.NavigateDirection.LastChild => GetChild(childCount - 1),
+ _ => base.FragmentNavigate(direction)
+ };
+ }
+
+ public override AccessibleObject? GetChild(int index)
+ {
+ if (!_owningListView.IsHandleCreated || index < 0 || index >= GetChildCount())
+ {
+ return null;
+ }
+
+ if (!OwnerHasGroups)
+ {
+ return _owningListView.Items[index].AccessibilityObject;
+ }
+
+ if (!OwnerHasDefaultGroup)
+ {
+ return _owningListView.Groups[index].AccessibilityObject;
+ }
+
+ // Default group has the last index out of the Groups.Count
+ // upper bound: so the DefaultGroup.Index == Groups.Count.
+ // But IMPORTANT: in the accessible tree the position of
+ // default group is the first before other groups.
+ return index == 0
+ ? _owningListView.DefaultGroup.AccessibilityObject
+ : _owningListView.Groups[index - 1].AccessibilityObject;
+ }
+
+ public override int GetChildCount()
+ {
+ if (!_owningListView.IsHandleCreated)
+ {
+ return 0;
+ }
+
+ if (_owningListView.Groups.Count > 0)
+ {
+ return OwnerHasDefaultGroup ? _owningListView.Groups.Count + 1 : _owningListView.Groups.Count;
+ }
+
+ return _owningListView.Items.Count;
+ }
+
+ internal int GetChildIndex(AccessibleObject child)
+ {
+ if (child is null)
+ {
+ return -1;
+ }
+
+ int childCount = GetChildCount();
+ for (int i = 0; i < childCount; i++)
+ {
+ AccessibleObject? currentChild = GetChild(i);
+ if (child == currentChild)
+ {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ private string GetItemStatus()
+ => _owningListView.Sorting switch
+ {
+ SortOrder.Ascending => SR.SortedAscendingAccessibleStatus,
+ SortOrder.Descending => SR.SortedDescendingAccessibleStatus,
+ _ => SR.NotSortedAccessibleStatus
+ };
+
+ internal override UiaCore.IRawElementProviderSimple[]? GetColumnHeaders()
+ {
+ UiaCore.IRawElementProviderSimple[] columnHeaders = new UiaCore.IRawElementProviderSimple[_owningListView.Columns.Count];
+ for (int i = 0; i < columnHeaders.Length; i++)
+ {
+ ColumnHeader columnHeader = _owningListView.Columns[i];
+ columnHeaders[i] = new ColumnHeader.ListViewColumnHeaderAccessibleObject(columnHeader);
+ }
+
+ return columnHeaders;
+ }
+
+ internal override UiaCore.IRawElementProviderFragment? GetFocus()
+ {
+ if (!_owningListView.IsHandleCreated)
+ {
+ return null;
+ }
+
+ return _owningListView.FocusedItem?.AccessibilityObject ?? _owningListView.FocusedGroup?.AccessibilityObject;
+ }
+
+ internal override int GetMultiViewProviderCurrentView()
+ => (int)_owningListView.View;
+
+ internal override int[] GetMultiViewProviderSupportedViews()
+ => new int[] { (int)View.Details };
+
+ internal override string GetMultiViewProviderViewName(int viewId)
+ {
+ foreach (int view in s_enumViewValues)
+ {
+ if (view == viewId)
+ {
+ return view.ToString();
+ }
+ }
+
+ return string.Empty;
+ }
+
+ internal AccessibleObject? GetNextChild(AccessibleObject currentChild)
+ {
+ int currentChildIndex = GetChildIndex(currentChild);
+ if (currentChildIndex == -1)
+ {
+ return null;
+ }
+
+ int childCount = GetChildCount();
+ if (currentChildIndex > childCount - 2) // is not the second to the last element.
+ {
+ return null;
+ }
+
+ return GetChild(currentChildIndex + 1);
+ }
+
+ internal AccessibleObject? GetPreviousChild(AccessibleObject currentChild)
+ {
+ int currentChildIndex = GetChildIndex(currentChild);
+ if (currentChildIndex <= 0)
+ {
+ return null;
+ }
+
+ return GetChild(currentChildIndex - 1);
+ }
+
+ internal override object? GetPropertyValue(UiaCore.UIA propertyID)
+ => propertyID switch
+ {
+ UiaCore.UIA.NamePropertyId => Name,
+ UiaCore.UIA.AutomationIdPropertyId => _owningListView.Name,
+ UiaCore.UIA.IsKeyboardFocusablePropertyId => (State & AccessibleStates.Focusable) == AccessibleStates.Focusable,
+ UiaCore.UIA.HasKeyboardFocusPropertyId => false,
+ UiaCore.UIA.RuntimeIdPropertyId => RuntimeId,
+ UiaCore.UIA.ControlTypePropertyId => UiaCore.UIA.ListControlTypeId,
+ UiaCore.UIA.IsMultipleViewPatternAvailablePropertyId => IsPatternSupported(UiaCore.UIA.MultipleViewPatternId),
+ UiaCore.UIA.ItemStatusPropertyId => GetItemStatus(),
+ _ => base.GetPropertyValue(propertyID)
+ };
+
+ internal override UiaCore.IRawElementProviderSimple[]? GetRowHeaders()
+ => null;
+
+ internal override UiaCore.IRawElementProviderSimple[] GetSelection()
+ {
+ if (!_owningListView.IsHandleCreated)
+ {
+ return Array.Empty();
+ }
+
+ UiaCore.IRawElementProviderSimple[] selectedItemProviders = new UiaCore.IRawElementProviderSimple[_owningListView.SelectedItems.Count];
+ for (int i = 0; i < selectedItemProviders.Length; i++)
+ {
+ selectedItemProviders[i] = _owningListView.SelectedItems[i].AccessibilityObject;
+ }
+
+ return selectedItemProviders;
+ }
+
+ public override AccessibleObject? HitTest(int x, int y)
+ {
+ if (!_owningListView.IsHandleCreated)
+ {
+ return null;
+ }
+
+ Point point = _owningListView.PointToClient(new Point(x, y));
+ ListViewHitTestInfo hitTestInfo = _owningListView.HitTest(point.X, point.Y);
+ if (hitTestInfo.Item is null && OwnerHasGroups)
+ {
+ for (int i = 0; i < GetChildCount(); i++)
+ {
+ AccessibleObject? accessibilityObject = GetChild(i);
+ if (accessibilityObject != null &&
+ accessibilityObject.Bounds.Contains(new Point(x, y)))
+ {
+ return accessibilityObject;
+ }
+ }
+
+ return null;
+ }
+
+ if (hitTestInfo.Item != null)
+ {
+ if (hitTestInfo.SubItem != null)
+ {
+ return hitTestInfo.SubItem.AccessibilityObject;
+ }
+
+ return hitTestInfo.Item.AccessibilityObject;
+ }
+
+ return null;
+ }
+
+ internal override bool IsPatternSupported(UiaCore.UIA patternId)
+ {
+ if (patternId == UiaCore.UIA.SelectionPatternId ||
+ patternId == UiaCore.UIA.MultipleViewPatternId ||
+ (patternId == UiaCore.UIA.GridPatternId && _owningListView.View == View.Details) ||
+ (patternId == UiaCore.UIA.TablePatternId && _owningListView.View == View.Details))
+ {
+ return true;
+ }
+
+ return base.IsPatternSupported(patternId);
+ }
+
+ internal override void SetMultiViewProviderCurrentView(int viewId)
+ {
+ foreach (var view in s_enumViewValues)
+ {
+ if (view == viewId)
+ {
+ _owningListView.View = (View)view;
+ }
+ }
+ }
+ }
+ }
+}
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 68f1447053e..a38f2751205 100644
--- a/src/System.Windows.Forms/src/System/Windows/Forms/ListView.cs
+++ b/src/System.Windows.Forms/src/System/Windows/Forms/ListView.cs
@@ -15,6 +15,7 @@
using System.Runtime.InteropServices;
using System.Windows.Forms.Layout;
using System.Windows.Forms.VisualStyles;
+using static System.Windows.Forms.ListViewGroup;
using static Interop;
using static Interop.ComCtl32;
@@ -29,7 +30,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;
@@ -127,6 +128,7 @@ public class ListView : Control
private int virtualListSize;
private ListViewGroup defaultGroup;
+ private ListViewGroup focusedGroup;
// Invariant: the table always contains all Items in the ListView, and maps IDs -> Items.
// listItemsArray is null if the handle is created; otherwise, it contains all Items.
@@ -777,7 +779,9 @@ internal ListViewGroup DefaultGroup
if (defaultGroup is null)
{
defaultGroup = new ListViewGroup(string.Format(SR.ListViewGroupDefaultGroup, "1"));
+ defaultGroup.ListView = this;
}
+
return defaultGroup;
}
}
@@ -815,6 +819,24 @@ internal bool ExpectingMouseUp
}
}
+ ///
+ /// Retreives the group which currently has the user focus. This is the
+ /// group that's drawn with the dotted focus rectangle around it.
+ /// Returns null if no group is currently focused.
+ ///
+ internal ListViewGroup FocusedGroup
+ {
+ get => IsHandleCreated ? focusedGroup : null;
+ set
+ {
+ if (IsHandleCreated && value != null)
+ {
+ value.Focused = true;
+ focusedGroup = value;
+ }
+ }
+ }
+
///
/// Retreives the item which currently has the user focus. This is the
/// item that's drawn with the dotted focus rectangle around it.
@@ -1671,6 +1693,8 @@ public ImageList StateImageList
}
}
+ internal override bool SupportsUiaProviders => true;
+
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
[Bindable(false)]
@@ -3492,6 +3516,21 @@ internal int GetColumnIndex(ColumnHeader ch)
return -1;
}
+ internal int GetGroupIndex(ListViewGroup owningGroup)
+ {
+ if (Groups.Count == 0)
+ {
+ return -1;
+ }
+ // Default group it's last group in accessibility and not specified in Groups collection, therefore default group index = Groups.Count
+ if (DefaultGroup == owningGroup)
+ {
+ return Groups.Count;
+ }
+
+ return Groups.IndexOf(owningGroup);
+ }
+
///
/// Returns the current ListViewItem corresponding to the specific
/// x,y co-ordinate.
@@ -4822,6 +4861,12 @@ protected virtual void OnSearchForVirtualItem(SearchForVirtualItemEventArgs e)
protected virtual void OnSelectedIndexChanged(EventArgs e)
{
((EventHandler)Events[EVENT_SELECTEDINDEXCHANGED])?.Invoke(this, e);
+
+ if (SelectedItems.Count > 0 && SelectedItems[0].Focused)
+ {
+ AccessibleObject accessibilityObject = SelectedItems[0].AccessibilityObject;
+ accessibilityObject?.RaiseAutomationEvent(UiaCore.UIA.AutomationFocusChangedEventId);
+ }
}
protected override void OnSystemColorsChanged(EventArgs e)
@@ -5857,6 +5902,10 @@ private void WmMouseDown(ref Message m, MouseButtons button, int clicks)
DefWndProc(ref m);
}
}
+
+ Point screenPoint = PointToScreen(new Point(x, y));
+ AccessibleObject accessibilityObject = AccessibilityObject.HitTest(screenPoint.X, screenPoint.Y);
+ accessibilityObject?.RaiseAutomationEvent(UiaCore.UIA.AutomationFocusChangedEventId);
}
private unsafe bool WmNotify(ref Message m)
@@ -6511,6 +6560,26 @@ private unsafe void WmReflectNotify(ref Message m)
break;
case (int)LVN.KEYDOWN:
+ if (Groups.Count > 0)
+ {
+ NMLVKEYDOWN* lvkd = (NMLVKEYDOWN*)m.LParam;
+ if (lvkd->wVKey == (short)Keys.Down
+ && SelectedItems.Count > 0
+ && SelectedItems[0].AccessibilityObject.FragmentNavigate(UiaCore.NavigateDirection.NextSibling) is null)
+ {
+ ListViewGroupAccessibleObject groupAccObj = (ListViewGroupAccessibleObject)SelectedItems[0].AccessibilityObject.FragmentNavigate(UiaCore.NavigateDirection.Parent);
+ ListViewGroupAccessibleObject nextGroupAccObj = (ListViewGroupAccessibleObject)groupAccObj.FragmentNavigate(UiaCore.NavigateDirection.NextSibling);
+ nextGroupAccObj?.SetFocus();
+ }
+
+ if (lvkd->wVKey == (short)Keys.Up
+ && SelectedItems.Count > 0
+ && SelectedItems[0].AccessibilityObject.FragmentNavigate(UiaCore.NavigateDirection.PreviousSibling) is null)
+ {
+ ListViewGroupAccessibleObject groupAccObj = (ListViewGroupAccessibleObject)SelectedItems[0].AccessibilityObject.FragmentNavigate(UiaCore.NavigateDirection.Parent);
+ groupAccObj?.SetFocus();
+ }
+ }
if (CheckBoxes)
{
NMLVKEYDOWN* lvkd = (NMLVKEYDOWN*)m.LParam;
@@ -9795,43 +9864,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/ListViewGroup.ListViewGroupAccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ListViewGroup.ListViewGroupAccessibleObject.cs
new file mode 100644
index 00000000000..74185dfde68
--- /dev/null
+++ b/src/System.Windows.Forms/src/System/Windows/Forms/ListViewGroup.ListViewGroupAccessibleObject.cs
@@ -0,0 +1,282 @@
+// 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;
+using static Interop.ComCtl32;
+
+namespace System.Windows.Forms
+{
+ public partial class ListViewGroup
+ {
+ internal class ListViewGroupAccessibleObject : AccessibleObject
+ {
+ private readonly ListView _owningListView;
+ private readonly ListViewGroup _owningGroup;
+ private readonly bool _owningGroupIsDefault;
+
+ public ListViewGroupAccessibleObject(ListViewGroup owningGroup, bool owningGroupIsDefault)
+ {
+ _owningGroup = owningGroup ?? throw new ArgumentNullException(nameof(owningGroup));
+ _owningListView = owningGroup.ListView ?? throw new InvalidOperationException(nameof(owningGroup.ListView));
+ _owningGroupIsDefault = owningGroupIsDefault;
+ }
+
+ private string AutomationId
+ => string.Format("{0}-{1}", typeof(ListViewGroup).Name, CurrentIndex);
+
+ public override Rectangle Bounds
+ {
+ get
+ {
+ if (!_owningListView.IsHandleCreated)
+ {
+ return Rectangle.Empty;
+ }
+
+ RECT groupRect = new RECT();
+ User32.SendMessageW(_owningListView, (User32.WM)ComCtl32.LVM.GETGROUPRECT, (IntPtr)CurrentIndex, ref groupRect);
+
+ return new Rectangle(
+ _owningListView.AccessibilityObject.Bounds.X + groupRect.left,
+ _owningListView.AccessibilityObject.Bounds.Y + groupRect.top,
+ groupRect.right - groupRect.left,
+ groupRect.bottom - groupRect.top);
+ }
+ }
+
+ private int CurrentIndex
+ => _owningGroupIsDefault
+ // Default group has the last index out of the Groups.Count
+ // upper bound: so the DefaultGroup.Index == Groups.Count.
+ ? _owningListView.Groups.Count
+ : _owningListView.Groups.IndexOf(_owningGroup);
+
+ public override string DefaultAction
+ => SR.AccessibleActionDoubleClick;
+
+ internal override UiaCore.ExpandCollapseState ExpandCollapseState
+ => _owningGroup.CollapsedState == ListViewGroupCollapsedState.Expanded
+ ? UiaCore.ExpandCollapseState.Expanded
+ : UiaCore.ExpandCollapseState.Collapsed;
+
+ public override string Name
+ => _owningGroup.Header;
+
+ public override AccessibleRole Role
+ => AccessibleRole.Grouping;
+
+ internal override int[]? RuntimeId
+ {
+ get
+ {
+ var owningListViewRuntimeId = _owningListView.AccessibilityObject.RuntimeId;
+ if (owningListViewRuntimeId is null)
+ {
+ return base.RuntimeId;
+ }
+
+ var runtimeId = new int[4];
+ runtimeId[0] = owningListViewRuntimeId[0];
+ runtimeId[1] = owningListViewRuntimeId[1];
+ runtimeId[2] = 4; // Win32-control specific RuntimeID constant, is used in similar Win32 controls and is used in WinForms controls for consistency.
+ runtimeId[3] = CurrentIndex;
+ return runtimeId;
+ }
+ }
+
+ public override AccessibleStates State
+ {
+ get
+ {
+ AccessibleStates state = AccessibleStates.Focusable;
+
+ if (Focused)
+ {
+ state |= AccessibleStates.Focused;
+ }
+
+ return state;
+ }
+ }
+
+ internal override void Collapse()
+ => _owningGroup.CollapsedState = ListViewGroupCollapsedState.Collapsed;
+
+ public override void DoDefaultAction() => SetFocus();
+
+ internal override void Expand()
+ => _owningGroup.CollapsedState = ListViewGroupCollapsedState.Expanded;
+
+ private bool Focused
+ => GetNativeFocus() || _owningGroup.Focused;
+
+ private bool GetNativeFocus()
+ {
+ if (!_owningListView.IsHandleCreated)
+ {
+ return false;
+ }
+
+ return LVGS.FOCUSED == unchecked((LVGS)(long)User32.SendMessageW(_owningListView, (User32.WM)LVM.GETGROUPSTATE, (IntPtr)CurrentIndex, (IntPtr)LVGS.FOCUSED));
+ }
+
+ internal override object? GetPropertyValue(UiaCore.UIA propertyID)
+ => propertyID switch
+ {
+ UiaCore.UIA.RuntimeIdPropertyId => RuntimeId,
+ UiaCore.UIA.AutomationIdPropertyId => AutomationId,
+ UiaCore.UIA.BoundingRectanglePropertyId => Bounds,
+ UiaCore.UIA.LegacyIAccessibleRolePropertyId => Role,
+ UiaCore.UIA.LegacyIAccessibleNamePropertyId => Name,
+ UiaCore.UIA.FrameworkIdPropertyId => NativeMethods.WinFormFrameworkId,
+ UiaCore.UIA.ControlTypePropertyId => UiaCore.UIA.GroupControlTypeId,
+ UiaCore.UIA.NamePropertyId => Name,
+ UiaCore.UIA.HasKeyboardFocusPropertyId => _owningListView.Focused && Focused,
+ UiaCore.UIA.IsKeyboardFocusablePropertyId => (State & AccessibleStates.Focusable) == AccessibleStates.Focusable,
+ UiaCore.UIA.IsEnabledPropertyId => _owningListView.Enabled,
+ UiaCore.UIA.IsOffscreenPropertyId => (State & AccessibleStates.Offscreen) == AccessibleStates.Offscreen,
+ UiaCore.UIA.NativeWindowHandlePropertyId => _owningListView.IsHandleCreated ? _owningListView.Handle : IntPtr.Zero,
+ UiaCore.UIA.IsLegacyIAccessiblePatternAvailablePropertyId => IsPatternSupported(UiaCore.UIA.LegacyIAccessiblePatternId),
+ _ => base.GetPropertyValue(propertyID)
+ };
+
+ internal override UiaCore.IRawElementProviderFragment? FragmentNavigate(UiaCore.NavigateDirection direction)
+ {
+ switch (direction)
+ {
+ case UiaCore.NavigateDirection.Parent:
+ return _owningListView.AccessibilityObject;
+ case UiaCore.NavigateDirection.NextSibling:
+ return (_owningListView.AccessibilityObject as ListView.ListViewAccessibleObject)?.GetNextChild(this);
+ case UiaCore.NavigateDirection.PreviousSibling:
+ return (_owningListView.AccessibilityObject as ListView.ListViewAccessibleObject)?.GetPreviousChild(this);
+ case UiaCore.NavigateDirection.FirstChild:
+ int childCount = GetChildCount();
+ if (childCount > 0)
+ {
+ return GetChild(0);
+ }
+
+ return null;
+ case UiaCore.NavigateDirection.LastChild:
+ childCount = GetChildCount();
+ if (childCount > 0)
+ {
+ return GetChild(childCount - 1);
+ }
+
+ return null;
+ default:
+ return null;
+ }
+ }
+
+ public override AccessibleObject? GetChild(int index)
+ {
+ if (!_owningGroupIsDefault)
+ {
+ if (index < 0 || index >= _owningGroup.Items.Count)
+ {
+ return null;
+ }
+
+ return _owningGroup.Items[index].AccessibilityObject;
+ }
+
+ foreach (ListViewItem? item in _owningListView.Items)
+ {
+ if (item != null && item.Group is null && index-- == 0)
+ {
+ return item.AccessibilityObject;
+ }
+ }
+
+ return null;
+ }
+
+ private int GetChildIndex(AccessibleObject child)
+ {
+ int childCount = GetChildCount();
+ for (int i = 0; i < childCount; i++)
+ {
+ var currentChild = GetChild(i);
+ if (child == currentChild)
+ {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ internal AccessibleObject? GetNextChild(AccessibleObject currentChild)
+ {
+ int currentChildIndex = GetChildIndex(currentChild);
+ if (currentChildIndex == -1)
+ {
+ return null;
+ }
+
+ int childCount = GetChildCount();
+ if (currentChildIndex > childCount - 2) // Is more than pre-last element.
+ {
+ return null;
+ }
+
+ return GetChild(currentChildIndex + 1);
+ }
+
+ internal AccessibleObject? GetPreviousChild(AccessibleObject currentChild)
+ {
+ int currentChildIndex = GetChildIndex(currentChild);
+ if (currentChildIndex <= 0)
+ {
+ return null;
+ }
+
+ return GetChild(currentChildIndex - 1);
+ }
+
+ public override int GetChildCount()
+ {
+ if (_owningGroupIsDefault)
+ {
+ int count = 0;
+ foreach (ListViewItem? item in _owningListView.Items)
+ {
+ if (item != null && item.Group is null)
+ {
+ count++;
+ }
+ }
+
+ return count;
+ }
+ else
+ {
+ return _owningGroup.Items.Count;
+ }
+ }
+
+ internal override bool IsPatternSupported(UiaCore.UIA patternId)
+ {
+ if (patternId == UiaCore.UIA.LegacyIAccessiblePatternId ||
+ patternId == UiaCore.UIA.ExpandCollapsePatternId)
+ {
+ return true;
+ }
+
+ return base.IsPatternSupported(patternId);
+ }
+
+ internal override unsafe void SetFocus()
+ {
+ _owningListView.FocusedGroup = _owningGroup;
+
+ RaiseAutomationEvent(UiaCore.UIA.AutomationFocusChangedEventId);
+ }
+ }
+ }
+}
diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ListViewGroup.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ListViewGroup.cs
index e5b6441025e..1ea562dc7db 100644
--- a/src/System.Windows.Forms/src/System/Windows/Forms/ListViewGroup.cs
+++ b/src/System.Windows.Forms/src/System/Windows/Forms/ListViewGroup.cs
@@ -17,7 +17,7 @@ namespace System.Windows.Forms
[DesignTimeVisible(false)]
[DefaultProperty(nameof(Header))]
[Serializable] // This type is participating in resx serialization scenarios.
- public sealed class ListViewGroup : ISerializable
+ public sealed partial class ListViewGroup : ISerializable
{
private string? _header;
private HorizontalAlignment _headerAlignment = HorizontalAlignment.Left;
@@ -34,6 +34,7 @@ public sealed class ListViewGroup : ISerializable
private static int s_nextHeader = 1;
private ListViewGroupImageIndexer? _imageIndexer;
+ private AccessibleObject? _accessibilityObject;
///
/// Creates a ListViewGroup.
@@ -76,6 +77,19 @@ public ListViewGroup(string? header, HorizontalAlignment headerAlignment) : this
_headerAlignment = headerAlignment;
}
+ internal AccessibleObject? AccessibilityObject
+ {
+ get
+ {
+ if (_accessibilityObject is null)
+ {
+ _accessibilityObject = new ListViewGroupAccessibleObject(this, ListView?.Groups.Contains(this) == false);
+ }
+
+ return _accessibilityObject;
+ }
+ }
+
///
/// The text displayed in the group header.
///
@@ -172,6 +186,8 @@ public HorizontalAlignment FooterAlignment
}
}
+ internal bool Focused { get; set; }
+
///
/// Controls which the group will appear as.
///
diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ListViewItem.ListViewItemAccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ListViewItem.ListViewItemAccessibleObject.cs
new file mode 100644
index 00000000000..966e31ec611
--- /dev/null
+++ b/src/System.Windows.Forms/src/System/Windows/Forms/ListViewItem.ListViewItemAccessibleObject.cs
@@ -0,0 +1,324 @@
+// 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 System.Runtime.InteropServices;
+using Accessibility;
+using static System.Windows.Forms.ListView;
+using static System.Windows.Forms.ListViewGroup;
+using static Interop;
+
+namespace System.Windows.Forms
+{
+ public partial class ListViewItem
+ {
+ internal class ListViewItemAccessibleObject : AccessibleObject
+ {
+ private readonly ListView _owningListView;
+ private readonly ListViewItem _owningItem;
+ private readonly ListViewGroup? _owningGroup;
+ private readonly IAccessible? _systemIAccessible;
+
+ public ListViewItemAccessibleObject(ListViewItem owningItem, ListViewGroup? owningGroup)
+ {
+ _owningItem = owningItem ?? throw new ArgumentNullException(nameof(owningItem));
+ _owningListView = owningItem.ListView ?? throw new InvalidOperationException(nameof(owningItem.ListView));
+ _owningGroup = owningGroup;
+ _systemIAccessible = _owningListView.AccessibilityObject.GetSystemIAccessibleInternal();
+ }
+
+ private string AutomationId
+ => string.Format("{0}-{1}", typeof(ListViewItem).Name, CurrentIndex);
+
+ public override Rectangle Bounds
+ => new Rectangle(
+ _owningListView.AccessibilityObject.Bounds.X + _owningItem.Bounds.X,
+ _owningListView.AccessibilityObject.Bounds.Y + _owningItem.Bounds.Y,
+ _owningItem.Bounds.Width,
+ _owningItem.Bounds.Height);
+
+ private int CurrentIndex
+ => _owningListView.Items.IndexOf(_owningItem);
+
+ internal override UiaCore.IRawElementProviderFragmentRoot FragmentRoot
+ => _owningListView.AccessibilityObject;
+
+ internal override bool IsItemSelected
+ => (State & AccessibleStates.Selected) != 0;
+
+ public override string? Name
+ {
+ get => _owningItem.Text;
+ set => base.Name = value;
+ }
+
+ private bool OwningListItemFocused
+ {
+ get
+ {
+ bool owningListViewFocused = _owningListView.Focused;
+ bool owningListItemFocused = _owningListView.FocusedItem == _owningItem;
+ return owningListViewFocused && owningListItemFocused;
+ }
+ }
+
+ ///
+ /// 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(_owningItem))
+ {
+ return state |= AccessibleStates.Selected | AccessibleStates.Focused;
+ }
+
+ if (_systemIAccessible != null)
+ {
+ return state |= (AccessibleStates)(_systemIAccessible.get_accState(GetChildId()));
+ }
+
+ return state;
+ }
+ }
+
+ internal override void AddToSelection()
+ => SelectItem();
+
+ public override string DefaultAction
+ => SR.AccessibleActionDoubleClick;
+
+ public override void DoDefaultAction()
+ => SetFocus();
+
+ internal override UiaCore.IRawElementProviderFragment? FragmentNavigate(UiaCore.NavigateDirection direction)
+ {
+ ListViewGroupAccessibleObject? owningGroupAccessibleObject = (ListViewGroupAccessibleObject?)_owningGroup?.AccessibilityObject;
+ switch (direction)
+ {
+ case UiaCore.NavigateDirection.Parent:
+ return owningGroupAccessibleObject ?? _owningListView.AccessibilityObject;
+ case UiaCore.NavigateDirection.NextSibling:
+ if (owningGroupAccessibleObject is null)
+ {
+ ListViewAccessibleObject listViewAccessibilityObject = (ListViewAccessibleObject)_owningListView.AccessibilityObject;
+ return listViewAccessibilityObject.GetNextChild(this);
+ }
+
+ return owningGroupAccessibleObject.GetNextChild(this);
+ case UiaCore.NavigateDirection.PreviousSibling:
+ if (owningGroupAccessibleObject is null)
+ {
+ ListViewAccessibleObject listViewAccessibilityObject = (ListViewAccessibleObject)_owningListView.AccessibilityObject;
+ return listViewAccessibilityObject.GetPreviousChild(this);
+ }
+
+ return owningGroupAccessibleObject.GetPreviousChild(this);
+ case UiaCore.NavigateDirection.FirstChild:
+ if (_owningItem.SubItems.Count > 0)
+ {
+ return GetChild(0);
+ }
+ break;
+ case UiaCore.NavigateDirection.LastChild:
+ int subItemsCount = _owningItem.SubItems.Count;
+ if (subItemsCount > 0)
+ {
+ return GetChild(subItemsCount - 1);
+ }
+ break;
+ }
+
+ return base.FragmentNavigate(direction);
+ }
+
+ public override AccessibleObject? GetChild(int index)
+ {
+ if (index < 0 || index >= _owningItem.SubItems.Count || _owningGroup != null)
+ {
+ return null;
+ }
+
+ return _owningItem.SubItems[index].AccessibilityObject;
+ }
+
+ internal override int[]? RuntimeId
+ {
+ get
+ {
+ int[] runtimeId;
+ var owningListViewRuntimeId = _owningListView.AccessibilityObject.RuntimeId;
+ if (owningListViewRuntimeId is null)
+ {
+ return base.RuntimeId;
+ }
+
+ if (_owningGroup != null)
+ {
+ runtimeId = new int[5];
+ runtimeId[0] = owningListViewRuntimeId[0];
+ runtimeId[1] = owningListViewRuntimeId[1];
+ runtimeId[2] = 4; // Win32-control specific RuntimeID constant, is used in similar Win32 controls and is used in WinForms controls for consistency.
+ runtimeId[3] = _owningListView.GetGroupIndex(_owningGroup);
+ runtimeId[4] = CurrentIndex;
+
+ return runtimeId;
+ }
+
+ runtimeId = new int[4];
+ runtimeId[0] = owningListViewRuntimeId[0];
+ runtimeId[1] = owningListViewRuntimeId[1];
+ runtimeId[2] = 4; // Win32-control specific RuntimeID constant.
+ runtimeId[3] = CurrentIndex;
+
+ return runtimeId;
+ }
+ }
+
+ internal override object? GetPropertyValue(UiaCore.UIA propertyID)
+ => propertyID switch
+ {
+ UiaCore.UIA.RuntimeIdPropertyId => RuntimeId,
+ UiaCore.UIA.AutomationIdPropertyId => AutomationId,
+ UiaCore.UIA.BoundingRectanglePropertyId => Bounds,
+ UiaCore.UIA.FrameworkIdPropertyId => NativeMethods.WinFormFrameworkId,
+ UiaCore.UIA.ControlTypePropertyId => UiaCore.UIA.ListItemControlTypeId,
+ UiaCore.UIA.NamePropertyId => Name,
+ UiaCore.UIA.HasKeyboardFocusPropertyId => OwningListItemFocused,
+ UiaCore.UIA.IsKeyboardFocusablePropertyId => (State & AccessibleStates.Focusable) == AccessibleStates.Focusable,
+ UiaCore.UIA.IsEnabledPropertyId => _owningListView.Enabled,
+ UiaCore.UIA.IsOffscreenPropertyId => (State & AccessibleStates.Offscreen) == AccessibleStates.Offscreen,
+ UiaCore.UIA.NativeWindowHandlePropertyId => _owningListView.IsHandleCreated ? _owningListView.Handle : IntPtr.Zero,
+ UiaCore.UIA.IsSelectionItemPatternAvailablePropertyId => IsPatternSupported(UiaCore.UIA.SelectionItemPatternId),
+ UiaCore.UIA.IsScrollItemPatternAvailablePropertyId => IsPatternSupported(UiaCore.UIA.ScrollItemPatternId),
+ UiaCore.UIA.IsInvokePatternAvailablePropertyId => IsPatternSupported(UiaCore.UIA.InvokePatternId),
+ _ => base.GetPropertyValue(propertyID)
+ };
+
+ ///
+ /// Indicates whether specified pattern is supported.
+ ///
+ /// The pattern ID.
+ /// True if specified
+ internal override bool IsPatternSupported(UiaCore.UIA patternId)
+ {
+ if (patternId == UiaCore.UIA.ScrollItemPatternId ||
+ patternId == UiaCore.UIA.LegacyIAccessiblePatternId ||
+ patternId == UiaCore.UIA.SelectionItemPatternId ||
+ patternId == UiaCore.UIA.InvokePatternId)
+ {
+ return true;
+ }
+
+ return base.IsPatternSupported(patternId);
+ }
+
+ internal override void RemoveFromSelection()
+ {
+ // Do nothing, C++ implementation returns UIA_E_INVALIDOPERATION 0x80131509
+ }
+
+ internal override UiaCore.IRawElementProviderSimple ItemSelectionContainer
+ => _owningListView.AccessibilityObject;
+
+ internal override void ScrollIntoView()
+ {
+ if (!_owningListView.IsHandleCreated)
+ {
+ return;
+ }
+
+ int currentIndex = CurrentIndex;
+
+ if (_owningListView.SelectedItems is null) // no items selected
+ {
+ User32.SendMessageW(_owningListView, (User32.WM)User32.LB.SETCARETINDEX, (IntPtr)currentIndex);
+ return;
+ }
+
+ int firstVisibleIndex = (int)(long)User32.SendMessageW(_owningListView, (User32.WM)User32.LB.GETTOPINDEX);
+ if (currentIndex < firstVisibleIndex)
+ {
+ User32.SendMessageW(_owningListView, (User32.WM)User32.LB.SETTOPINDEX, (IntPtr)currentIndex);
+ return;
+ }
+
+ int itemsHeightSum = 0;
+ int listBoxHeight = _owningListView.ClientRectangle.Height;
+ int itemsCount = _owningListView.Items.Count;
+
+ for (int i = firstVisibleIndex; i < itemsCount; i++)
+ {
+ int itemHeight = PARAM.ToInt(User32.SendMessageW(_owningListView, (User32.WM)User32.LB.GETITEMHEIGHT, (IntPtr)i));
+
+ itemsHeightSum += itemHeight;
+
+ if (itemsHeightSum <= listBoxHeight)
+ {
+ continue;
+ }
+
+ int lastVisibleIndex = i - 1; // less 1 because last "i" index is invisible
+ int visibleItemsCount = lastVisibleIndex - firstVisibleIndex + 1; // add 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()
+ {
+ if (_owningListView.IsHandleCreated)
+ {
+ _owningListView.SelectedIndices.Add(CurrentIndex);
+ User32.InvalidateRect(new HandleRef(this, _owningListView.Handle), null, BOOL.FALSE);
+ }
+
+ RaiseAutomationEvent(UiaCore.UIA.AutomationFocusChangedEventId);
+ RaiseAutomationEvent(UiaCore.UIA.SelectionItem_ElementSelectedEventId);
+ }
+
+ internal override void SetFocus()
+ {
+ RaiseAutomationEvent(UiaCore.UIA.AutomationFocusChangedEventId);
+ SelectItem();
+ }
+
+ public override void Select(AccessibleSelection flags)
+ {
+ if (!_owningListView.IsHandleCreated)
+ {
+ return;
+ }
+
+ 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/ListViewItem.ListViewSubItem.ListViewSubItemAccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ListViewItem.ListViewSubItem.ListViewSubItemAccessibleObject.cs
new file mode 100644
index 00000000000..4dcc9ec0c39
--- /dev/null
+++ b/src/System.Windows.Forms/src/System/Windows/Forms/ListViewItem.ListViewSubItem.ListViewSubItemAccessibleObject.cs
@@ -0,0 +1,165 @@
+// 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.Diagnostics;
+using System.Drawing;
+using static Interop;
+
+namespace System.Windows.Forms
+{
+ public partial class ListViewItem
+ {
+ public partial class ListViewSubItem
+ {
+ internal class ListViewSubItemAccessibleObject : AccessibleObject
+ {
+ private readonly ListView _owningListView;
+ private readonly ListViewItem _owningItem;
+ private readonly ListViewSubItem _owningSubItem;
+
+ public ListViewSubItemAccessibleObject(ListViewSubItem owningSubItem, ListViewItem owningItem)
+ {
+ _owningSubItem = owningSubItem ?? throw new ArgumentNullException(nameof(owningSubItem));
+ _owningItem = owningItem ?? throw new ArgumentNullException(nameof(owningItem));
+ _owningListView = owningItem.ListView ?? throw new InvalidOperationException(nameof(owningItem.ListView));
+ }
+
+ internal override UiaCore.IRawElementProviderFragmentRoot FragmentRoot
+ => _owningListView.AccessibilityObject;
+
+ public override Rectangle Bounds
+ {
+ get
+ {
+ // Previously bounds was provided using MSAA,
+ // but using UIA we found out that SendMessageW work incorrectly.
+ // When we need to get bounds for first sub item it will return width of all item.
+ int width = _owningSubItem.Bounds.Width;
+
+ if (Column == 0 && _owningItem.SubItems.Count > 1)
+ {
+ width = _owningItem.SubItems[Column + 1].Bounds.X - _owningSubItem.Bounds.X;
+ }
+
+ return new Rectangle(
+ _owningListView.AccessibilityObject.Bounds.X + _owningSubItem.Bounds.X,
+ _owningListView.AccessibilityObject.Bounds.Y + _owningSubItem.Bounds.Y,
+ width, _owningSubItem.Bounds.Height);
+ }
+ }
+
+ internal override UiaCore.IRawElementProviderFragment? FragmentNavigate(UiaCore.NavigateDirection direction)
+ {
+ switch (direction)
+ {
+ case UiaCore.NavigateDirection.Parent:
+ return _owningItem.AccessibilityObject;
+ case UiaCore.NavigateDirection.NextSibling:
+ int nextSubItemIndex = GetCurrentSubItemIndex() + 1;
+ if (_owningItem.SubItems.Count > nextSubItemIndex)
+ {
+ return _owningItem.SubItems[nextSubItemIndex].AccessibilityObject;
+ }
+ break;
+ case UiaCore.NavigateDirection.PreviousSibling:
+ int previousSubItemIndex = GetCurrentSubItemIndex() - 1;
+ if (previousSubItemIndex >= 0)
+ {
+ return _owningItem.SubItems[previousSubItemIndex].AccessibilityObject;
+ }
+ break;
+ }
+
+ return base.FragmentNavigate(direction);
+ }
+
+ ///
+ /// Gets or sets the accessible name.
+ ///
+ public override string? Name
+ {
+ get => base.Name ?? _owningSubItem.Text;
+ set => base.Name = value;
+ }
+
+ public override AccessibleObject Parent
+ => _owningItem.AccessibilityObject;
+
+ internal override int[]? RuntimeId
+ {
+ get
+ {
+ var owningItemRuntimeId = Parent.RuntimeId;
+ if (owningItemRuntimeId is null)
+ {
+ return base.RuntimeId;
+ }
+
+ var runtimeId = new int[5];
+ runtimeId[0] = owningItemRuntimeId[0];
+ runtimeId[1] = owningItemRuntimeId[1];
+ runtimeId[2] = owningItemRuntimeId[2];
+ runtimeId[3] = owningItemRuntimeId[3];
+ runtimeId[4] = GetCurrentSubItemIndex();
+ return runtimeId;
+ }
+ }
+
+ internal override object? GetPropertyValue(UiaCore.UIA propertyID)
+ => propertyID switch
+ {
+ UiaCore.UIA.ControlTypePropertyId => UiaCore.UIA.TextControlTypeId,
+ UiaCore.UIA.NamePropertyId => Name,
+ UiaCore.UIA.FrameworkIdPropertyId => NativeMethods.WinFormFrameworkId,
+ UiaCore.UIA.ProcessIdPropertyId => Process.GetCurrentProcess().Id,
+ UiaCore.UIA.AutomationIdPropertyId => AutomationId,
+ UiaCore.UIA.RuntimeIdPropertyId => RuntimeId,
+ UiaCore.UIA.HasKeyboardFocusPropertyId => _owningListView.Focused && _owningListView.FocusedItem == _owningItem,
+ UiaCore.UIA.IsKeyboardFocusablePropertyId => (State & AccessibleStates.Focusable) == AccessibleStates.Focusable,
+ UiaCore.UIA.IsEnabledPropertyId => _owningListView.Enabled,
+ UiaCore.UIA.IsOffscreenPropertyId => (State & AccessibleStates.Offscreen) == AccessibleStates.Offscreen,
+ UiaCore.UIA.BoundingRectanglePropertyId => Bounds,
+ UiaCore.UIA.IsGridItemPatternAvailablePropertyId => IsPatternSupported(UiaCore.UIA.GridItemPatternId),
+ UiaCore.UIA.IsTableItemPatternAvailablePropertyId => IsPatternSupported(UiaCore.UIA.TableItemPatternId),
+ _ => base.GetPropertyValue(propertyID)
+ };
+
+ ///
+ /// Gets the accessible state.
+ ///
+ public override AccessibleStates State
+ => AccessibleStates.Focusable;
+
+ internal override UiaCore.IRawElementProviderSimple ContainingGrid
+ => _owningListView.AccessibilityObject;
+
+ internal override int Row
+ => _owningItem.Index;
+
+ internal override int Column
+ => _owningItem.SubItems.IndexOf(_owningSubItem);
+
+ internal override UiaCore.IRawElementProviderSimple[]? GetColumnHeaderItems()
+ => new UiaCore.IRawElementProviderSimple[] { _owningListView.Columns[Column].AccessibilityObject };
+
+ internal override bool IsPatternSupported(UiaCore.UIA patternId)
+ {
+ if (patternId == UiaCore.UIA.GridItemPatternId ||
+ patternId == UiaCore.UIA.TableItemPatternId)
+ {
+ return true;
+ }
+
+ return base.IsPatternSupported(patternId);
+ }
+
+ private string AutomationId
+ => string.Format("{0}-{1}", typeof(ListViewItem.ListViewSubItem).Name, GetCurrentSubItemIndex());
+
+ private int GetCurrentSubItemIndex()
+ => _owningItem.SubItems.IndexOf(_owningSubItem);
+ }
+ }
+ }
+}
diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ListViewItem.ListViewSubItem.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ListViewItem.ListViewSubItem.cs
new file mode 100644
index 00000000000..fd9ea270f35
--- /dev/null
+++ b/src/System.Windows.Forms/src/System/Windows/Forms/ListViewItem.ListViewSubItem.cs
@@ -0,0 +1,291 @@
+// 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.ComponentModel;
+using System.Diagnostics;
+using System.Drawing;
+using System.Runtime.Serialization;
+
+namespace System.Windows.Forms
+{
+ public partial class ListViewItem
+ {
+ [TypeConverter(typeof(ListViewSubItemConverter))]
+ [ToolboxItem(false)]
+ [DesignTimeVisible(false)]
+ [DefaultProperty(nameof(Text))]
+ [Serializable] // This type is participating in resx serialization scenarios.
+ public partial class ListViewSubItem
+ {
+ [NonSerialized]
+ internal ListViewItem owner;
+#pragma warning disable IDE1006
+ private string text; // Do NOT rename (binary serialization).
+
+ [OptionalField(VersionAdded = 2)]
+ private string name = null; // Do NOT rename (binary serialization).
+
+ private SubItemStyle style; // Do NOT rename (binary serialization).
+
+ [OptionalField(VersionAdded = 2)]
+ private object userData; // Do NOT rename (binary serialization).
+#pragma warning restore IDE1006
+
+ [NonSerialized]
+ private AccessibleObject _accessibilityObject;
+
+ public ListViewSubItem()
+ {
+ }
+
+ public ListViewSubItem(ListViewItem owner, string text)
+ {
+ this.owner = owner;
+ this.text = text;
+ }
+
+ public ListViewSubItem(ListViewItem owner, string text, Color foreColor, Color backColor, Font font)
+ {
+ this.owner = owner;
+ this.text = text;
+ style = new SubItemStyle
+ {
+ foreColor = foreColor,
+ backColor = backColor,
+ font = font
+ };
+ }
+
+ internal AccessibleObject AccessibilityObject
+ {
+ get
+ {
+ if (_accessibilityObject is null)
+ {
+ _accessibilityObject = new ListViewSubItemAccessibleObject(this, owner);
+ }
+
+ return _accessibilityObject;
+ }
+ }
+
+ public Color BackColor
+ {
+ get
+ {
+ if (style != null && style.backColor != Color.Empty)
+ {
+ return style.backColor;
+ }
+
+ if (owner != null && owner.listView != null)
+ {
+ return owner.listView.BackColor;
+ }
+
+ return SystemColors.Window;
+ }
+ set
+ {
+ if (style is null)
+ {
+ style = new SubItemStyle();
+ }
+
+ if (style.backColor != value)
+ {
+ style.backColor = value;
+ owner?.InvalidateListView();
+ }
+ }
+ }
+ [Browsable(false)]
+ public Rectangle Bounds
+ {
+ get
+ {
+ if (owner != null && owner.listView != null && owner.listView.IsHandleCreated)
+ {
+ return owner.listView.GetSubItemRect(owner.Index, owner.SubItems.IndexOf(this));
+ }
+ else
+ {
+ return Rectangle.Empty;
+ }
+ }
+ }
+
+ internal bool CustomBackColor
+ {
+ get
+ {
+ Debug.Assert(style != null, "Should have checked CustomStyle");
+ return !style.backColor.IsEmpty;
+ }
+ }
+
+ internal bool CustomFont
+ {
+ get
+ {
+ Debug.Assert(style != null, "Should have checked CustomStyle");
+ return style.font != null;
+ }
+ }
+
+ internal bool CustomForeColor
+ {
+ get
+ {
+ Debug.Assert(style != null, "Should have checked CustomStyle");
+ return !style.foreColor.IsEmpty;
+ }
+ }
+
+ internal bool CustomStyle => style != null;
+
+ [Localizable(true)]
+ public Font Font
+ {
+ get
+ {
+ if (style != null && style.font != null)
+ {
+ return style.font;
+ }
+
+ if (owner != null && owner.listView != null)
+ {
+ return owner.listView.Font;
+ }
+
+ return Control.DefaultFont;
+ }
+ set
+ {
+ if (style is null)
+ {
+ style = new SubItemStyle();
+ }
+
+ if (style.font != value)
+ {
+ style.font = value;
+ owner?.InvalidateListView();
+ }
+ }
+ }
+
+ public Color ForeColor
+ {
+ get
+ {
+ if (style != null && style.foreColor != Color.Empty)
+ {
+ return style.foreColor;
+ }
+
+ if (owner != null && owner.listView != null)
+ {
+ return owner.listView.ForeColor;
+ }
+
+ return SystemColors.WindowText;
+ }
+ set
+ {
+ if (style is null)
+ {
+ style = new SubItemStyle();
+ }
+
+ if (style.foreColor != value)
+ {
+ style.foreColor = value;
+ owner?.InvalidateListView();
+ }
+ }
+ }
+
+ [SRCategory(nameof(SR.CatData))]
+ [Localizable(false)]
+ [Bindable(true)]
+ [SRDescription(nameof(SR.ControlTagDescr))]
+ [DefaultValue(null)]
+ [TypeConverter(typeof(StringConverter))]
+ public object Tag
+ {
+ get => userData;
+ set => userData = value;
+ }
+
+ [Localizable(true)]
+ public string Text
+ {
+ get => text ?? string.Empty;
+ set
+ {
+ text = value;
+ owner?.UpdateSubItems(-1);
+ }
+ }
+
+ [Localizable(true)]
+ public string Name
+ {
+ get => name ?? string.Empty;
+ set
+ {
+ name = value;
+ owner?.UpdateSubItems(-1);
+ }
+ }
+
+ [OnDeserializing]
+ private void OnDeserializing(StreamingContext ctx)
+ {
+ }
+
+ [OnDeserialized]
+ private void OnDeserialized(StreamingContext ctx)
+ {
+ name = null;
+ userData = null;
+ }
+
+ [OnSerializing]
+ private void OnSerializing(StreamingContext ctx)
+ {
+ }
+
+ [OnSerialized]
+ private void OnSerialized(StreamingContext ctx)
+ {
+ }
+
+ public void ResetStyle()
+ {
+ if (style != null)
+ {
+ style = null;
+ owner?.InvalidateListView();
+ }
+ }
+
+ public override string ToString() => "ListViewSubItem: {" + Text + "}";
+
+ [Serializable] // This type is participating in resx serialization scenarios.
+ private class SubItemStyle
+ {
+#pragma warning disable IDE1006
+ public Color backColor = Color.Empty; // Do NOT rename (binary serialization).
+ public Color foreColor = Color.Empty; // Do NOT rename (binary serialization).
+ public Font font; // Do NOT rename (binary serialization).
+#pragma warning restore IDE1006
+ }
+ }
+ }
+}
diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ListViewItem.ListViewSubItemCollection.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ListViewItem.ListViewSubItemCollection.cs
new file mode 100644
index 00000000000..621f9ffe16d
--- /dev/null
+++ b/src/System.Windows.Forms/src/System/Windows/Forms/ListViewItem.ListViewSubItemCollection.cs
@@ -0,0 +1,451 @@
+// 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;
+using System.ComponentModel;
+using System.Drawing;
+
+namespace System.Windows.Forms
+{
+ public partial class ListViewItem
+ {
+ public class ListViewSubItemCollection : IList
+ {
+ private readonly ListViewItem _owner;
+
+ // A caching mechanism for key accessor
+ // We use an index here rather than control so that we don't have lifetime
+ // issues by holding on to extra references.
+ private int _lastAccessedIndex = -1;
+
+ public ListViewSubItemCollection(ListViewItem owner)
+ {
+ _owner = owner ?? throw new ArgumentNullException(nameof(owner));
+ }
+
+ ///
+ /// Returns the total number of items within the list view.
+ ///
+ [Browsable(false)]
+ public int Count => _owner.SubItemCount;
+
+ object ICollection.SyncRoot => this;
+
+ bool ICollection.IsSynchronized => true;
+
+ bool IList.IsFixedSize => false;
+
+ public bool IsReadOnly => false;
+
+ ///
+ /// Returns a ListViewSubItem given it's zero based index into the ListViewSubItemCollection.
+ ///
+ public ListViewSubItem this[int index]
+ {
+ get
+ {
+ if (index < 0 || index >= Count)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), index, string.Format(SR.InvalidArgument, nameof(index), index));
+ }
+
+ return _owner.subItems[index];
+ }
+ set
+ {
+ if (index < 0 || index >= Count)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), index, string.Format(SR.InvalidArgument, nameof(index), index));
+ }
+
+ _owner.subItems[index] = value ?? throw new ArgumentNullException(nameof(value));
+ _owner.UpdateSubItems(index);
+ }
+ }
+
+ object IList.this[int index]
+ {
+ get => this[index];
+ set
+ {
+ if (!(value is ListViewSubItem item))
+ {
+ throw new ArgumentException(SR.ListViewBadListViewSubItem, nameof(value));
+ }
+
+ this[index] = item;
+ }
+ }
+ ///
+ /// Retrieves the child control with the specified key.
+ ///
+ public virtual ListViewSubItem this[string key]
+ {
+ get
+ {
+ // We do not support null and empty string as valid keys.
+ if (string.IsNullOrEmpty(key))
+ {
+ return null;
+ }
+
+ // Search for the key in our collection
+ int index = IndexOfKey(key);
+ if (!IsValidIndex(index))
+ {
+ return null;
+ }
+
+ return this[index];
+ }
+ }
+
+ public ListViewSubItem Add(ListViewSubItem item)
+ {
+ if (item is null)
+ {
+ throw new ArgumentNullException(nameof(item));
+ }
+
+ EnsureSubItemSpace(1, -1);
+ item.owner = _owner;
+ _owner.subItems[_owner.SubItemCount] = item;
+ _owner.UpdateSubItems(_owner.SubItemCount++);
+ return item;
+ }
+
+ public ListViewSubItem Add(string text)
+ {
+ ListViewSubItem item = new ListViewSubItem(_owner, text);
+ Add(item);
+ return item;
+ }
+
+ public ListViewSubItem Add(string text, Color foreColor, Color backColor, Font font)
+ {
+ ListViewSubItem item = new ListViewSubItem(_owner, text, foreColor, backColor, font);
+ Add(item);
+ return item;
+ }
+
+ public void AddRange(ListViewSubItem[] items)
+ {
+ if (items is null)
+ {
+ throw new ArgumentNullException(nameof(items));
+ }
+
+ EnsureSubItemSpace(items.Length, -1);
+ foreach (ListViewSubItem item in items)
+ {
+ if (item != null)
+ {
+ _owner.subItems[_owner.SubItemCount++] = item;
+ }
+ }
+
+ _owner.UpdateSubItems(-1);
+ }
+
+ public void AddRange(string[] items)
+ {
+ if (items is null)
+ {
+ throw new ArgumentNullException(nameof(items));
+ }
+
+ EnsureSubItemSpace(items.Length, -1);
+ foreach (string item in items)
+ {
+ if (item != null)
+ {
+ _owner.subItems[_owner.SubItemCount++] = new ListViewSubItem(_owner, item);
+ }
+ }
+
+ _owner.UpdateSubItems(-1);
+ }
+
+ public void AddRange(string[] items, Color foreColor, Color backColor, Font font)
+ {
+ if (items is null)
+ {
+ throw new ArgumentNullException(nameof(items));
+ }
+
+ EnsureSubItemSpace(items.Length, -1);
+ foreach (string item in items)
+ {
+ if (item != null)
+ {
+ _owner.subItems[_owner.SubItemCount++] = new ListViewSubItem(_owner, item, foreColor, backColor, font);
+ }
+ }
+
+ _owner.UpdateSubItems(-1);
+ }
+
+ int IList.Add(object item)
+ {
+ if (!(item is ListViewSubItem itemValue))
+ {
+ throw new ArgumentException(SR.ListViewSubItemCollectionInvalidArgument, nameof(item));
+ }
+
+ return IndexOf(Add(itemValue));
+ }
+
+ public void Clear()
+ {
+ int oldCount = _owner.SubItemCount;
+ if (oldCount > 0)
+ {
+ _owner.SubItemCount = 0;
+ _owner.UpdateSubItems(-1, oldCount);
+ }
+ }
+
+ public bool Contains(ListViewSubItem subItem) => IndexOf(subItem) != -1;
+
+ bool IList.Contains(object item)
+ {
+ if (!(item is ListViewSubItem itemValue))
+ {
+ return false;
+ }
+
+ return Contains(itemValue);
+ }
+
+ ///
+ /// Returns true if the collection contains an item with the specified key, false otherwise.
+ ///
+ public virtual bool ContainsKey(string key) => IsValidIndex(IndexOfKey(key));
+
+ ///
+ /// Ensures that the sub item array has the given
+ /// capacity. If it doesn't, it enlarges the
+ /// array until it does. If index is -1, additional
+ /// space is tacked onto the end. If it is a valid
+ /// insertion index into the array, this will move
+ /// the array data to accomodate the space.
+ ///
+ private void EnsureSubItemSpace(int size, int index)
+ {
+ if (_owner.SubItemCount == MaxSubItems)
+ {
+ throw new InvalidOperationException(SR.ErrorCollectionFull);
+ }
+
+ if (_owner.subItems is null || _owner.SubItemCount + size > _owner.subItems.Length)
+ {
+ // Must grow array. Don't do it just by size, though;
+ // chunk it for efficiency.
+ if (_owner.subItems is null)
+ {
+ int newSize = (size > 4) ? size : 4;
+ _owner.subItems = new ListViewSubItem[newSize];
+ }
+ else
+ {
+ int newSize = _owner.subItems.Length * 2;
+ while (newSize - _owner.SubItemCount < size)
+ {
+ newSize *= 2;
+ }
+
+ ListViewSubItem[] newItems = new ListViewSubItem[newSize];
+
+ // Now, when copying to the member variable, use index
+ // if it was provided.
+ if (index != -1)
+ {
+ Array.Copy(_owner.subItems, 0, newItems, 0, index);
+ Array.Copy(_owner.subItems, index, newItems, index + size, _owner.SubItemCount - index);
+ }
+ else
+ {
+ Array.Copy(_owner.subItems, newItems, _owner.SubItemCount);
+ }
+ _owner.subItems = newItems;
+ }
+ }
+ else
+ {
+ // We had plenty of room. Just move the items if we need to
+ if (index != -1)
+ {
+ for (int i = _owner.SubItemCount - 1; i >= index; i--)
+ {
+ _owner.subItems[i + size] = _owner.subItems[i];
+ }
+ }
+ }
+ }
+
+ public int IndexOf(ListViewSubItem subItem)
+ {
+ for (int index = 0; index < Count; ++index)
+ {
+ if (_owner.subItems[index] == subItem)
+ {
+ return index;
+ }
+ }
+
+ return -1;
+ }
+
+ int IList.IndexOf(object subItem)
+ {
+ if (!(subItem is ListViewSubItem subItemValue))
+ {
+ return -1;
+ }
+
+ return IndexOf(subItemValue);
+ }
+
+ ///
+ /// The zero-based index of the first occurrence of value within the entire CollectionBase, if found; otherwise, -1.
+ ///
+ public virtual int IndexOfKey(string key)
+ {
+ if (string.IsNullOrEmpty(key))
+ {
+ return -1;
+ }
+
+ if (IsValidIndex(_lastAccessedIndex))
+ {
+ if (WindowsFormsUtils.SafeCompareStrings(this[_lastAccessedIndex].Name, key, ignoreCase: true))
+ {
+ return _lastAccessedIndex;
+ }
+ }
+
+ for (int i = 0; i < Count; i++)
+ {
+ if (WindowsFormsUtils.SafeCompareStrings(this[i].Name, key, ignoreCase: true))
+ {
+ _lastAccessedIndex = i;
+ return i;
+ }
+ }
+
+ _lastAccessedIndex = -1;
+ return -1;
+ }
+
+ ///
+ /// Determines if the index is valid for the collection.
+ ///
+ private bool IsValidIndex(int index) => ((index >= 0) && (index < Count));
+
+ public void Insert(int index, ListViewSubItem item)
+ {
+ if (index < 0 || index > Count)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+ if (item is null)
+ {
+ throw new ArgumentNullException(nameof(item));
+ }
+
+ item.owner = _owner;
+
+ EnsureSubItemSpace(1, index);
+
+ // Insert new item
+ _owner.subItems[index] = item;
+ _owner.SubItemCount++;
+ _owner.UpdateSubItems(-1);
+ }
+
+ void IList.Insert(int index, object item)
+ {
+ if (!(item is ListViewSubItem itemValue))
+ {
+ throw new ArgumentException(SR.ListViewBadListViewSubItem, nameof(item));
+ }
+
+ Insert(index, (ListViewSubItem)item);
+ }
+
+ public void Remove(ListViewSubItem item)
+ {
+ int index = IndexOf(item);
+ if (index != -1)
+ {
+ RemoveAt(index);
+ }
+ }
+
+ void IList.Remove(object item)
+ {
+ if (item is ListViewSubItem itemValue)
+ {
+ Remove(itemValue);
+ }
+ }
+
+ public void RemoveAt(int index)
+ {
+ if (index < 0 || index >= Count)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+
+ // Remove ourselves as the owner.
+ _owner.subItems[index].owner = null;
+
+ // Collapse the items
+ for (int i = index + 1; i < _owner.SubItemCount; i++)
+ {
+ _owner.subItems[i - 1] = _owner.subItems[i];
+ }
+
+ int oldCount = _owner.SubItemCount;
+ _owner.SubItemCount--;
+ _owner.subItems[_owner.SubItemCount] = null;
+ _owner.UpdateSubItems(-1, oldCount);
+ }
+
+ ///
+ /// Removes the child control with the specified key.
+ ///
+ public virtual void RemoveByKey(string key)
+ {
+ int index = IndexOfKey(key);
+ if (IsValidIndex(index))
+ {
+ RemoveAt(index);
+ }
+ }
+
+ void ICollection.CopyTo(Array dest, int index)
+ {
+ if (Count > 0)
+ {
+ Array.Copy(_owner.subItems, 0, dest, index, Count);
+ }
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ if (_owner.subItems != null)
+ {
+ return new ArraySubsetEnumerator(_owner.subItems, _owner.SubItemCount);
+ }
+ else
+ {
+ return Array.Empty().GetEnumerator();
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ListViewItem.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ListViewItem.cs
index 989787b7981..9a354d0833c 100644
--- a/src/System.Windows.Forms/src/System/Windows/Forms/ListViewItem.cs
+++ b/src/System.Windows.Forms/src/System/Windows/Forms/ListViewItem.cs
@@ -4,7 +4,6 @@
#nullable disable
-using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
@@ -25,7 +24,7 @@ namespace System.Windows.Forms
[DesignTimeVisible(false)]
[DefaultProperty(nameof(Text))]
[Serializable] // This type is participating in resx serialization scenarios.
- public class ListViewItem : ICloneable, ISerializable
+ public partial class ListViewItem : ICloneable, ISerializable
{
private const int MaxSubItems = 4096;
@@ -57,6 +56,8 @@ public class ListViewItem : ICloneable, ISerializable
private string toolTipText = string.Empty;
private object userData;
+ private AccessibleObject _accessibilityObject;
+
public ListViewItem()
{
StateSelected = false;
@@ -227,6 +228,22 @@ public ListViewItem(ListViewSubItem[] subItems, string imageKey, ListViewGroup g
Group = group;
}
+ internal AccessibleObject AccessibilityObject
+ {
+ get
+ {
+ if (_accessibilityObject is null)
+ {
+ bool inDefaultGroup = listView?.GroupsEnabled == true && Group is null;
+
+ _accessibilityObject = new ListViewItemAccessibleObject(
+ this, inDefaultGroup ? listView.DefaultGroup : Group);
+ }
+
+ return _accessibilityObject;
+ }
+ }
+
///
/// The font that this item will be displayed in. If its value is null, it will be displayed
/// using the global font for the ListView control that hosts it.
@@ -328,6 +345,8 @@ public bool Focused
if (listView != null && listView.IsHandleCreated)
{
listView.SetItemState(Index, value ? LVIS.FOCUSED : 0, LVIS.FOCUSED);
+
+ AccessibilityObject.RaiseAutomationEvent(UiaCore.UIA.AutomationFocusChangedEventId);
}
}
}
@@ -1275,700 +1294,5 @@ void ISerializable.GetObjectData(SerializationInfo info, StreamingContext contex
{
Serialize(info, context);
}
-
- [TypeConverter(typeof(ListViewSubItemConverter))]
- [ToolboxItem(false)]
- [DesignTimeVisible(false)]
- [DefaultProperty(nameof(Text))]
- [Serializable] // This type is participating in resx serialization scenarios.
- public class ListViewSubItem
- {
- [NonSerialized]
- internal ListViewItem owner;
-#pragma warning disable IDE1006
- private string text; // Do NOT rename (binary serialization).
-
- [OptionalField(VersionAdded = 2)]
- private string name = null; // Do NOT rename (binary serialization).
-
- private SubItemStyle style; // Do NOT rename (binary serialization).
-
- [OptionalField(VersionAdded = 2)]
- private object userData; // Do NOT rename (binary serialization).
-#pragma warning restore IDE1006
-
- public ListViewSubItem()
- {
- }
-
- public ListViewSubItem(ListViewItem owner, string text)
- {
- this.owner = owner;
- this.text = text;
- }
-
- public ListViewSubItem(ListViewItem owner, string text, Color foreColor, Color backColor, Font font)
- {
- this.owner = owner;
- this.text = text;
- style = new SubItemStyle
- {
- foreColor = foreColor,
- backColor = backColor,
- font = font
- };
- }
-
- public Color BackColor
- {
- get
- {
- if (style != null && style.backColor != Color.Empty)
- {
- return style.backColor;
- }
-
- if (owner != null && owner.listView != null)
- {
- return owner.listView.BackColor;
- }
-
- return SystemColors.Window;
- }
- set
- {
- if (style is null)
- {
- style = new SubItemStyle();
- }
-
- if (style.backColor != value)
- {
- style.backColor = value;
- owner?.InvalidateListView();
- }
- }
- }
- [Browsable(false)]
- public Rectangle Bounds
- {
- get
- {
- if (owner != null && owner.listView != null && owner.listView.IsHandleCreated)
- {
- return owner.listView.GetSubItemRect(owner.Index, owner.SubItems.IndexOf(this));
- }
- else
- {
- return Rectangle.Empty;
- }
- }
- }
-
- internal bool CustomBackColor
- {
- get
- {
- Debug.Assert(style != null, "Should have checked CustomStyle");
- return !style.backColor.IsEmpty;
- }
- }
-
- internal bool CustomFont
- {
- get
- {
- Debug.Assert(style != null, "Should have checked CustomStyle");
- return style.font != null;
- }
- }
-
- internal bool CustomForeColor
- {
- get
- {
- Debug.Assert(style != null, "Should have checked CustomStyle");
- return !style.foreColor.IsEmpty;
- }
- }
-
- internal bool CustomStyle => style != null;
-
- [Localizable(true)]
- public Font Font
- {
- get
- {
- if (style != null && style.font != null)
- {
- return style.font;
- }
-
- if (owner != null && owner.listView != null)
- {
- return owner.listView.Font;
- }
-
- return Control.DefaultFont;
- }
- set
- {
- if (style is null)
- {
- style = new SubItemStyle();
- }
-
- if (style.font != value)
- {
- style.font = value;
- owner?.InvalidateListView();
- }
- }
- }
-
- public Color ForeColor
- {
- get
- {
- if (style != null && style.foreColor != Color.Empty)
- {
- return style.foreColor;
- }
-
- if (owner != null && owner.listView != null)
- {
- return owner.listView.ForeColor;
- }
-
- return SystemColors.WindowText;
- }
- set
- {
- if (style is null)
- {
- style = new SubItemStyle();
- }
-
- if (style.foreColor != value)
- {
- style.foreColor = value;
- owner?.InvalidateListView();
- }
- }
- }
-
- [SRCategory(nameof(SR.CatData))]
- [Localizable(false)]
- [Bindable(true)]
- [SRDescription(nameof(SR.ControlTagDescr))]
- [DefaultValue(null)]
- [TypeConverter(typeof(StringConverter))]
- public object Tag
- {
- get => userData;
- set => userData = value;
- }
-
- [Localizable(true)]
- public string Text
- {
- get => text ?? string.Empty;
- set
- {
- text = value;
- owner?.UpdateSubItems(-1);
- }
- }
-
- [Localizable(true)]
- public string Name
- {
- get => name ?? string.Empty;
- set
- {
- name = value;
- owner?.UpdateSubItems(-1);
- }
- }
-
- [OnDeserializing]
- private void OnDeserializing(StreamingContext ctx)
- {
- }
-
- [OnDeserialized]
- private void OnDeserialized(StreamingContext ctx)
- {
- name = null;
- userData = null;
- }
-
- [OnSerializing]
- private void OnSerializing(StreamingContext ctx)
- {
- }
-
- [OnSerialized]
- private void OnSerialized(StreamingContext ctx)
- {
- }
-
- public void ResetStyle()
- {
- if (style != null)
- {
- style = null;
- owner?.InvalidateListView();
- }
- }
-
- public override string ToString() => "ListViewSubItem: {" + Text + "}";
-
- [Serializable] // This type is participating in resx serialization scenarios.
- private class SubItemStyle
- {
-#pragma warning disable IDE1006
- public Color backColor = Color.Empty; // Do NOT rename (binary serialization).
- public Color foreColor = Color.Empty; // Do NOT rename (binary serialization).
- public Font font; // Do NOT rename (binary serialization).
-#pragma warning restore IDE1006
- }
- }
-
- public class ListViewSubItemCollection : IList
- {
- private readonly ListViewItem _owner;
-
- // A caching mechanism for key accessor
- // We use an index here rather than control so that we don't have lifetime
- // issues by holding on to extra references.
- private int _lastAccessedIndex = -1;
-
- public ListViewSubItemCollection(ListViewItem owner)
- {
- _owner = owner ?? throw new ArgumentNullException(nameof(owner));
- }
-
- ///
- /// Returns the total number of items within the list view.
- ///
- [Browsable(false)]
- public int Count => _owner.SubItemCount;
-
- object ICollection.SyncRoot => this;
-
- bool ICollection.IsSynchronized => true;
-
- bool IList.IsFixedSize => false;
-
- public bool IsReadOnly => false;
-
- ///
- /// Returns a ListViewSubItem given it's zero based index into the ListViewSubItemCollection.
- ///
- public ListViewSubItem this[int index]
- {
- get
- {
- if (index < 0 || index >= Count)
- {
- throw new ArgumentOutOfRangeException(nameof(index), index, string.Format(SR.InvalidArgument, nameof(index), index));
- }
-
- return _owner.subItems[index];
- }
- set
- {
- if (index < 0 || index >= Count)
- {
- throw new ArgumentOutOfRangeException(nameof(index), index, string.Format(SR.InvalidArgument, nameof(index), index));
- }
-
- _owner.subItems[index] = value ?? throw new ArgumentNullException(nameof(value));
- _owner.UpdateSubItems(index);
- }
- }
-
- object IList.this[int index]
- {
- get => this[index];
- set
- {
- if (!(value is ListViewSubItem item))
- {
- throw new ArgumentException(SR.ListViewBadListViewSubItem, nameof(value));
- }
-
- this[index] = item;
- }
- }
- ///
- /// Retrieves the child control with the specified key.
- ///
- public virtual ListViewSubItem this[string key]
- {
- get
- {
- // We do not support null and empty string as valid keys.
- if (string.IsNullOrEmpty(key))
- {
- return null;
- }
-
- // Search for the key in our collection
- int index = IndexOfKey(key);
- if (!IsValidIndex(index))
- {
- return null;
- }
-
- return this[index];
- }
- }
-
- public ListViewSubItem Add(ListViewSubItem item)
- {
- if (item is null)
- {
- throw new ArgumentNullException(nameof(item));
- }
-
- EnsureSubItemSpace(1, -1);
- item.owner = _owner;
- _owner.subItems[_owner.SubItemCount] = item;
- _owner.UpdateSubItems(_owner.SubItemCount++);
- return item;
- }
-
- public ListViewSubItem Add(string text)
- {
- ListViewSubItem item = new ListViewSubItem(_owner, text);
- Add(item);
- return item;
- }
-
- public ListViewSubItem Add(string text, Color foreColor, Color backColor, Font font)
- {
- ListViewSubItem item = new ListViewSubItem(_owner, text, foreColor, backColor, font);
- Add(item);
- return item;
- }
-
- public void AddRange(ListViewSubItem[] items)
- {
- if (items is null)
- {
- throw new ArgumentNullException(nameof(items));
- }
-
- EnsureSubItemSpace(items.Length, -1);
- foreach (ListViewSubItem item in items)
- {
- if (item != null)
- {
- _owner.subItems[_owner.SubItemCount++] = item;
- }
- }
-
- _owner.UpdateSubItems(-1);
- }
-
- public void AddRange(string[] items)
- {
- if (items is null)
- {
- throw new ArgumentNullException(nameof(items));
- }
-
- EnsureSubItemSpace(items.Length, -1);
- foreach (string item in items)
- {
- if (item != null)
- {
- _owner.subItems[_owner.SubItemCount++] = new ListViewSubItem(_owner, item);
- }
- }
-
- _owner.UpdateSubItems(-1);
- }
-
- public void AddRange(string[] items, Color foreColor, Color backColor, Font font)
- {
- if (items is null)
- {
- throw new ArgumentNullException(nameof(items));
- }
-
- EnsureSubItemSpace(items.Length, -1);
- foreach (string item in items)
- {
- if (item != null)
- {
- _owner.subItems[_owner.SubItemCount++] = new ListViewSubItem(_owner, item, foreColor, backColor, font);
- }
- }
-
- _owner.UpdateSubItems(-1);
- }
-
- int IList.Add(object item)
- {
- if (!(item is ListViewSubItem itemValue))
- {
- throw new ArgumentException(SR.ListViewSubItemCollectionInvalidArgument, nameof(item));
- }
-
- return IndexOf(Add(itemValue));
- }
-
- public void Clear()
- {
- int oldCount = _owner.SubItemCount;
- if (oldCount > 0)
- {
- _owner.SubItemCount = 0;
- _owner.UpdateSubItems(-1, oldCount);
- }
- }
-
- public bool Contains(ListViewSubItem subItem) => IndexOf(subItem) != -1;
-
- bool IList.Contains(object item)
- {
- if (!(item is ListViewSubItem itemValue))
- {
- return false;
- }
-
- return Contains(itemValue);
- }
-
- ///
- /// Returns true if the collection contains an item with the specified key, false otherwise.
- ///
- public virtual bool ContainsKey(string key) => IsValidIndex(IndexOfKey(key));
-
- ///
- /// Ensures that the sub item array has the given
- /// capacity. If it doesn't, it enlarges the
- /// array until it does. If index is -1, additional
- /// space is tacked onto the end. If it is a valid
- /// insertion index into the array, this will move
- /// the array data to accomodate the space.
- ///
- private void EnsureSubItemSpace(int size, int index)
- {
- if (_owner.SubItemCount == MaxSubItems)
- {
- throw new InvalidOperationException(SR.ErrorCollectionFull);
- }
-
- if (_owner.subItems is null || _owner.SubItemCount + size > _owner.subItems.Length)
- {
- // Must grow array. Don't do it just by size, though;
- // chunk it for efficiency.
- if (_owner.subItems is null)
- {
- int newSize = (size > 4) ? size : 4;
- _owner.subItems = new ListViewSubItem[newSize];
- }
- else
- {
- int newSize = _owner.subItems.Length * 2;
- while (newSize - _owner.SubItemCount < size)
- {
- newSize *= 2;
- }
-
- ListViewSubItem[] newItems = new ListViewSubItem[newSize];
-
- // Now, when copying to the member variable, use index
- // if it was provided.
- if (index != -1)
- {
- Array.Copy(_owner.subItems, 0, newItems, 0, index);
- Array.Copy(_owner.subItems, index, newItems, index + size, _owner.SubItemCount - index);
- }
- else
- {
- Array.Copy(_owner.subItems, newItems, _owner.SubItemCount);
- }
- _owner.subItems = newItems;
- }
- }
- else
- {
- // We had plenty of room. Just move the items if we need to
- if (index != -1)
- {
- for (int i = _owner.SubItemCount - 1; i >= index; i--)
- {
- _owner.subItems[i + size] = _owner.subItems[i];
- }
- }
- }
- }
-
- public int IndexOf(ListViewSubItem subItem)
- {
- for (int index = 0; index < Count; ++index)
- {
- if (_owner.subItems[index] == subItem)
- {
- return index;
- }
- }
-
- return -1;
- }
-
- int IList.IndexOf(object subItem)
- {
- if (!(subItem is ListViewSubItem subItemValue))
- {
- return -1;
- }
-
- return IndexOf(subItemValue);
- }
-
- ///
- /// The zero-based index of the first occurrence of value within the entire CollectionBase, if found; otherwise, -1.
- ///
- public virtual int IndexOfKey(string key)
- {
- if (string.IsNullOrEmpty(key))
- {
- return -1;
- }
-
- if (IsValidIndex(_lastAccessedIndex))
- {
- if (WindowsFormsUtils.SafeCompareStrings(this[_lastAccessedIndex].Name, key, ignoreCase: true))
- {
- return _lastAccessedIndex;
- }
- }
-
- for (int i = 0; i < Count; i++)
- {
- if (WindowsFormsUtils.SafeCompareStrings(this[i].Name, key, ignoreCase: true))
- {
- _lastAccessedIndex = i;
- return i;
- }
- }
-
- _lastAccessedIndex = -1;
- return -1;
- }
-
- ///
- /// Determines if the index is valid for the collection.
- ///
- private bool IsValidIndex(int index) => ((index >= 0) && (index < Count));
-
- public void Insert(int index, ListViewSubItem item)
- {
- if (index < 0 || index > Count)
- {
- throw new ArgumentOutOfRangeException(nameof(index));
- }
- if (item is null)
- {
- throw new ArgumentNullException(nameof(item));
- }
-
- item.owner = _owner;
-
- EnsureSubItemSpace(1, index);
-
- // Insert new item
- _owner.subItems[index] = item;
- _owner.SubItemCount++;
- _owner.UpdateSubItems(-1);
- }
-
- void IList.Insert(int index, object item)
- {
- if (!(item is ListViewSubItem itemValue))
- {
- throw new ArgumentException(SR.ListViewBadListViewSubItem, nameof(item));
- }
-
- Insert(index, (ListViewSubItem)item);
- }
-
- public void Remove(ListViewSubItem item)
- {
- int index = IndexOf(item);
- if (index != -1)
- {
- RemoveAt(index);
- }
- }
-
- void IList.Remove(object item)
- {
- if (item is ListViewSubItem itemValue)
- {
- Remove(itemValue);
- }
- }
-
- public void RemoveAt(int index)
- {
- if (index < 0 || index >= Count)
- {
- throw new ArgumentOutOfRangeException(nameof(index));
- }
-
- // Remove ourselves as the owner.
- _owner.subItems[index].owner = null;
-
- // Collapse the items
- for (int i = index + 1; i < _owner.SubItemCount; i++)
- {
- _owner.subItems[i - 1] = _owner.subItems[i];
- }
-
- int oldCount = _owner.SubItemCount;
- _owner.SubItemCount--;
- _owner.subItems[_owner.SubItemCount] = null;
- _owner.UpdateSubItems(-1, oldCount);
- }
-
- ///
- /// Removes the child control with the specified key.
- ///
- public virtual void RemoveByKey(string key)
- {
- int index = IndexOfKey(key);
- if (IsValidIndex(index))
- {
- RemoveAt(index);
- }
- }
-
- void ICollection.CopyTo(Array dest, int index)
- {
- if (Count > 0)
- {
- Array.Copy(_owner.subItems, 0, dest, index, Count);
- }
- }
-
- public IEnumerator GetEnumerator()
- {
- if (_owner.subItems != null)
- {
- return new ArraySubsetEnumerator(_owner.subItems, _owner.SubItemCount);
- }
- else
- {
- return Array.Empty().GetEnumerator();
- }
- }
- }
}
}
diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ListVIew.ListViewAccessibleObjectTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ListVIew.ListViewAccessibleObjectTests.cs
new file mode 100644
index 00000000000..6426909b2f9
--- /dev/null
+++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ListVIew.ListViewAccessibleObjectTests.cs
@@ -0,0 +1,180 @@
+// 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 System.Windows.Forms.ListViewGroup;
+using static System.Windows.Forms.ListViewItem;
+using static Interop;
+
+namespace System.Windows.Forms.Tests
+{
+ public class ListView_ListViewAccessibleObjectTests
+ {
+ [WinFormsFact]
+ public void ListViewAccessibleObject_Ctor_Default()
+ {
+ using ListView listView = new ListView();
+
+ AccessibleObject accessibleObject = listView.AccessibilityObject;
+ Assert.True(listView.IsHandleCreated);
+ Assert.NotNull(accessibleObject);
+ Assert.Equal(AccessibleRole.List, accessibleObject.Role);
+ }
+
+ [WinFormsFact]
+ public void ListViewAccessibleObject_EmptyList_GetChildCount_ReturnsCorrectValue()
+ {
+ using ListView listView = new ListView();
+ AccessibleObject accessibleObject = listView.AccessibilityObject;
+ Assert.True(listView.IsHandleCreated);
+ Assert.Equal(0, accessibleObject.GetChildCount()); // listView doesn't have items
+ }
+
+ [WinFormsFact]
+ public void ListViewAccessibleObject_GetMultiViewProviderCurrentView_ReturnsCorrectValue()
+ {
+ using ListView listView = new ListView();
+ AccessibleObject accessibleObject = listView.AccessibilityObject;
+ Assert.True(listView.IsHandleCreated);
+ Assert.Equal((int)listView.View, accessibleObject.GetMultiViewProviderCurrentView());
+ }
+
+ [WinFormsFact]
+ public void ListViewAccessibleObject_GetMultiViewProviderSupportedViews_ReturnsExpected()
+ {
+ using ListView listView = new ListView();
+ AccessibleObject accessibleObject = listView.AccessibilityObject;
+ Assert.True(listView.IsHandleCreated);
+ Assert.Equal(new int[] { (int)View.Details }, accessibleObject.GetMultiViewProviderSupportedViews());
+ }
+
+ [WinFormsFact]
+ public void ListViewAccessibleObject_GetMultiViewProviderViewName_ReturnsCorrectValue()
+ {
+ using ListView listView = new ListView();
+ listView.View = View.Details;
+ AccessibleObject accessibleObject = listView.AccessibilityObject;
+ Assert.True(listView.IsHandleCreated);
+ Assert.Equal(((int)(listView.View)).ToString(), accessibleObject.GetMultiViewProviderViewName((int)View.Details));
+ }
+
+ [WinFormsFact]
+ public void ListViewAccessibleObject_ListWithOneItem_GetChildCount_ReturnsCorrectValue()
+ {
+ using ListView listView = new ListView();
+ AccessibleObject accessibleObject = listView.AccessibilityObject;
+ Assert.True(listView.IsHandleCreated);
+ listView.Items.Add(new ListViewItem());
+ Assert.Equal(1, accessibleObject.GetChildCount()); // One item
+ }
+
+ [WinFormsFact]
+ public void ListViewAccessibleObject_ListWithTwoGroups_GetChildCount_ReturnsCorrectValue()
+ {
+ using ListView listView = new ListView();
+ listView.Items.Add(new ListViewItem());
+ ListViewItem item = new ListViewItem();
+ ListViewItem item2 = new ListViewItem();
+ ListViewGroup group = new ListViewGroup();
+ item2.Group = group;
+ item.Group = group;
+ listView.Groups.Add(group);
+ listView.Items.Add(item);
+ listView.Items.Add(item2);
+
+ AccessibleObject accessibleObject = listView.AccessibilityObject;
+ Assert.True(listView.IsHandleCreated);
+ Assert.Equal(2, accessibleObject.GetChildCount()); // Default group and one specified group
+ }
+
+ [WinFormsFact]
+ public void ListViewAccessibleObject_ListWithTwoGroups_FragmentNavigateWorkCorrectly()
+ {
+ using ListView listView = new ListView();
+ listView.Items.Add(new ListViewItem());
+ ListViewItem item = new ListViewItem();
+ ListViewItem item2 = new ListViewItem();
+ ListViewGroup group = new ListViewGroup();
+ item2.Group = group;
+ item.Group = group;
+ listView.Groups.Add(group);
+ listView.Items.Add(item);
+ listView.Items.Add(item2);
+
+ AccessibleObject accessibleObject = listView.AccessibilityObject;
+ Assert.True(listView.IsHandleCreated);
+
+ AccessibleObject firstChild = accessibleObject.FragmentNavigate(UiaCore.NavigateDirection.FirstChild) as AccessibleObject;
+ AccessibleObject lastChild = accessibleObject.FragmentNavigate(UiaCore.NavigateDirection.LastChild) as AccessibleObject;
+ Assert.IsType(firstChild);
+ Assert.IsType(lastChild);
+ Assert.NotEqual(firstChild, lastChild);
+ }
+
+ [WinFormsFact]
+ public void ListViewAccessibleObject_ListWithTwoItems_GetChildCount_ReturnsCorrectValue()
+ {
+ using ListView listView = new ListView();
+ listView.Items.Add(new ListViewItem());
+ listView.Items.Add(new ListViewItem());
+
+ AccessibleObject accessibleObject = listView.AccessibilityObject;
+ Assert.True(listView.IsHandleCreated);
+ Assert.Equal(2, accessibleObject.GetChildCount()); // Two child items
+ }
+
+ [WinFormsFact]
+ public void ListViewAccessibleObject_ListWithTwoItems_FragmentNavigateWorkCorrectly()
+ {
+ using ListView listView = new ListView();
+ listView.Items.Add(new ListViewItem());
+ listView.Items.Add(new ListViewItem());
+
+ AccessibleObject accessibleObject = listView.AccessibilityObject;
+ Assert.True(listView.IsHandleCreated);
+
+ AccessibleObject firstChild = accessibleObject.FragmentNavigate(UiaCore.NavigateDirection.FirstChild) as AccessibleObject;
+ AccessibleObject lastChild = accessibleObject.FragmentNavigate(UiaCore.NavigateDirection.LastChild) as AccessibleObject;
+ Assert.IsType(firstChild);
+ Assert.IsType(lastChild);
+ Assert.NotEqual(firstChild, lastChild);
+ }
+
+ [WinFormsFact]
+ public void ListViewAccessibleObject_EmptyList_GetChild_ReturnsCorrectValue()
+ {
+ using ListView listView = new ListView();
+ AccessibleObject accessibleObject = listView.AccessibilityObject;
+ Assert.True(listView.IsHandleCreated);
+ Assert.Equal(0, accessibleObject.GetChildCount()); // listView doesn't have items
+ Assert.Null(accessibleObject.GetChild(0)); // GetChild method should not throw an exception
+ }
+
+ [WinFormsFact]
+ public void ListViewAccessibleObject_GetPropertyValue_returns_correct_values()
+ {
+ using var list = new ListView();
+ list.Name = "List";
+ list.AccessibleName = "ListView";
+ AccessibleObject listAccessibleObject = list.AccessibilityObject;
+ Assert.True(list.IsHandleCreated);
+
+ object accessibleName = listAccessibleObject.GetPropertyValue(UiaCore.UIA.NamePropertyId);
+ Assert.Equal("ListView", accessibleName);
+
+ object automationId = listAccessibleObject.GetPropertyValue(UiaCore.UIA.AutomationIdPropertyId);
+ Assert.Equal("List", automationId);
+
+ object accessibleControlType = listAccessibleObject.GetPropertyValue(UiaCore.UIA.ControlTypePropertyId);
+ Assert.Equal(UiaCore.UIA.ListControlTypeId, accessibleControlType);
+
+ object controlType = listAccessibleObject.GetPropertyValue(UiaCore.UIA.ControlTypePropertyId);
+ UiaCore.UIA expected = UiaCore.UIA.ListControlTypeId;
+ Assert.Equal(expected, controlType);
+
+ Assert.True((bool)listAccessibleObject.GetPropertyValue(UiaCore.UIA.IsMultipleViewPatternAvailablePropertyId));
+ }
+ }
+}
+
diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ListViewGroup.ListViewGroupAccessibleObjectTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ListViewGroup.ListViewGroupAccessibleObjectTests.cs
new file mode 100644
index 00000000000..d20681818b1
--- /dev/null
+++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ListViewGroup.ListViewGroupAccessibleObjectTests.cs
@@ -0,0 +1,169 @@
+// 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.Diagnostics;
+using System.Drawing;
+using System.Reflection;
+using System.Threading;
+using Microsoft.DotNet.RemoteExecutor;
+using Xunit;
+using static System.Windows.Forms.ListViewGroup;
+using static System.Windows.Forms.ListViewItem;
+using static Interop;
+
+namespace System.Windows.Forms.Tests
+{
+ public class ListViewGroup_ListViewGroupAccessibleObjectTests
+ {
+ [WinFormsFact]
+ public void ListViewGroupAccessibleObject_Ctor_ThrowsArgumentNullException()
+ {
+ using ListView list = new ListView();
+ ListViewGroup listGroup = new ListViewGroup("Group1");
+ listGroup.Items.Add(new ListViewItem());
+ list.Groups.Add(listGroup);
+
+ Type type = listGroup.AccessibilityObject.GetType();
+ ConstructorInfo ctor = type.GetConstructor(new Type[] { typeof(ListViewGroup), typeof(bool) });
+ Assert.NotNull(ctor);
+ Assert.Throws(() => ctor.Invoke(new object[] { null, false }));
+
+ // group without parent ListView
+ ListViewGroup listGroupWithoutList = new ListViewGroup("Group2");
+ Assert.Throws(() => ctor.Invoke(new object[] { listGroupWithoutList, false }));
+ }
+
+ [WinFormsFact]
+ public void ListViewGroupAccessibleObject_Ctor_Default()
+ {
+ using ListView list = new ListView();
+ ListViewGroup listGroup = new ListViewGroup("Group1");
+ listGroup.Items.Add(new ListViewItem());
+ list.Groups.Add(listGroup);
+
+ AccessibleObject accessibleObject = listGroup.AccessibilityObject;
+ Assert.False(list.IsHandleCreated);
+
+ Assert.NotNull(accessibleObject);
+ Assert.Equal(AccessibleRole.Grouping, accessibleObject.Role);
+ }
+
+ [WinFormsFact]
+ public void ListViewGroupAccessibleObject_GetPropertyValue_returns_correct_values()
+ {
+ using ListView list = new ListView();
+ ListViewGroup listGroup = new ListViewGroup("Group1");
+ listGroup.Items.Add(new ListViewItem());
+ list.Groups.Add(listGroup);
+
+ AccessibleObject accessibleObject = listGroup.AccessibilityObject;
+ Assert.False(list.IsHandleCreated);
+
+ object accessibleName = accessibleObject.GetPropertyValue(UiaCore.UIA.NamePropertyId);
+ Assert.Equal("Group1", accessibleName);
+
+ object automationId = accessibleObject.GetPropertyValue(UiaCore.UIA.AutomationIdPropertyId);
+ Assert.Equal("ListViewGroup-0", automationId);
+
+ object controlType = accessibleObject.GetPropertyValue(UiaCore.UIA.ControlTypePropertyId);
+ UiaCore.UIA expected = UiaCore.UIA.GroupControlTypeId;
+ Assert.Equal(expected, controlType);
+
+ Assert.True((bool)accessibleObject.GetPropertyValue(UiaCore.UIA.IsLegacyIAccessiblePatternAvailablePropertyId));
+ }
+
+ [WinFormsFact]
+ public void ListViewGroupAccessibleObject_ListWithTwoGroups_FragmentNavigateWorkCorrectly()
+ {
+ using ListView list = new ListView();
+ ListViewGroup listGroup = new ListViewGroup("Group1");
+ ListViewItem listItem1 = new ListViewItem();
+ ListViewItem listItem2 = new ListViewItem();
+ ListViewItem listItem3 = new ListViewItem();
+ list.Groups.Add(listGroup);
+ listItem1.Group = listGroup;
+ listItem2.Group = listGroup;
+ list.Items.Add(listItem1);
+ list.Items.Add(listItem2);
+ list.Items.Add(listItem3);
+ AccessibleObject group1AccObj = listGroup.AccessibilityObject;
+ AccessibleObject defaultGroupAccObj = list.DefaultGroup.AccessibilityObject;
+ Assert.False(list.IsHandleCreated);
+
+ // Next/Previous siblings test
+ Assert.Null(defaultGroupAccObj.FragmentNavigate(UiaCore.NavigateDirection.PreviousSibling));
+ UiaCore.IRawElementProviderFragment defaultGroupNextSibling = defaultGroupAccObj.FragmentNavigate(UiaCore.NavigateDirection.NextSibling);
+ Assert.IsType(group1AccObj);
+ Assert.Equal(group1AccObj, defaultGroupNextSibling);
+
+ Assert.Null(group1AccObj.FragmentNavigate(UiaCore.NavigateDirection.NextSibling));
+ UiaCore.IRawElementProviderFragment group1PreviousSibling = group1AccObj.FragmentNavigate(UiaCore.NavigateDirection.PreviousSibling);
+ Assert.IsType(group1PreviousSibling);
+ Assert.Equal(defaultGroupAccObj, group1PreviousSibling);
+
+ // Parent
+ Assert.Equal(defaultGroupAccObj.FragmentNavigate(UiaCore.NavigateDirection.Parent), list.AccessibilityObject);
+ Assert.Equal(group1AccObj.FragmentNavigate(UiaCore.NavigateDirection.Parent), list.AccessibilityObject);
+
+ // Childs
+ AccessibleObject firstChild = group1AccObj.FragmentNavigate(UiaCore.NavigateDirection.FirstChild) as AccessibleObject;
+ AccessibleObject lastChild = group1AccObj.FragmentNavigate(UiaCore.NavigateDirection.LastChild) as AccessibleObject;
+ Assert.IsType(firstChild);
+ Assert.IsType(lastChild);
+ Assert.NotEqual(firstChild, lastChild);
+ Assert.Equal(firstChild, listItem1.AccessibilityObject);
+ Assert.Equal(lastChild, listItem2.AccessibilityObject);
+
+ firstChild = defaultGroupAccObj.FragmentNavigate(UiaCore.NavigateDirection.FirstChild) as AccessibleObject;
+ lastChild = defaultGroupAccObj.FragmentNavigate(UiaCore.NavigateDirection.LastChild) as AccessibleObject;
+ Assert.IsType(firstChild);
+ Assert.IsType(lastChild);
+ Assert.Equal(firstChild, lastChild);
+ Assert.Equal(firstChild, listItem3.AccessibilityObject);
+ }
+
+ [WinFormsFact]
+ public void ListViewGroupAccessibleObject_Bounds_ReturnsCorrectValue()
+ {
+ using RemoteInvokeHandle invokerHandle = RemoteExecutor.Invoke(() =>
+ {
+ Control.CheckForIllegalCrossThreadCalls = true;
+ using Form form = new Form();
+
+ using ListView list = new ListView();
+ ListViewGroup listGroup = new ListViewGroup("Group1");
+ ListViewItem listItem1 = new ListViewItem("Item1");
+ ListViewItem listItem2 = new ListViewItem("Item2");
+ list.Groups.Add(listGroup);
+ listItem1.Group = listGroup;
+ listItem2.Group = listGroup;
+ list.Items.Add(listItem1);
+ list.Items.Add(listItem2);
+ list.CreateControl();
+ form.Controls.Add(list);
+ form.Show();
+
+ AccessibleObject accessibleObject = list.AccessibilityObject;
+ AccessibleObject group1AccObj = listGroup.AccessibilityObject;
+ Assert.True(list.IsHandleCreated);
+
+ RECT groupRect = new RECT();
+ User32.SendMessageW(list, (User32.WM)ComCtl32.LVM.GETGROUPRECT, (IntPtr)list.Groups.IndexOf(listGroup), ref groupRect);
+
+ int actualWidth = group1AccObj.Bounds.Width;
+ int expectedWidth = groupRect.Width;
+ Assert.Equal(expectedWidth, actualWidth);
+
+ int actualHeight = group1AccObj.Bounds.Height;
+ int expectedHeight = groupRect.Height;
+ Assert.Equal(expectedHeight, actualHeight);
+
+ Rectangle actualBounds = group1AccObj.Bounds;
+ actualBounds.Location = new Point(0, 0);
+ Rectangle expectedBounds = groupRect;
+ Assert.Equal(expectedBounds, actualBounds);
+ });
+ }
+ }
+}
diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ListViewItem.ListViewItemAccessibleObjectTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ListViewItem.ListViewItemAccessibleObjectTests.cs
new file mode 100644
index 00000000000..9f91dab38b0
--- /dev/null
+++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ListViewItem.ListViewItemAccessibleObjectTests.cs
@@ -0,0 +1,123 @@
+// 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.Reflection;
+using Xunit;
+using static System.Windows.Forms.ListViewItem;
+using static System.Windows.Forms.ListViewItem.ListViewSubItem;
+using static Interop;
+
+namespace System.Windows.Forms.Tests
+{
+ public class ListViewItem_ListViewItemAccessibleObjectTests
+ {
+ [WinFormsFact]
+ public void ListViewItemAccessibleObject_Ctor_ThrowsArgumentNullException()
+ {
+ using ListView list = new ListView();
+ ListViewItem listItem = new ListViewItem();
+ list.Items.Add(listItem);
+
+ Type type = listItem.AccessibilityObject.GetType();
+ ConstructorInfo ctor = type.GetConstructor(new Type[] { typeof(ListViewItem), typeof(ListViewGroup) });
+ Assert.NotNull(ctor);
+ Assert.Throws(() => ctor.Invoke(new object[] { null, null }));
+
+ // item without parent ListView
+ ListViewItem itemWithoutList = new ListViewItem();
+ Assert.Throws(() => ctor.Invoke(new object[] { itemWithoutList, null }));
+ }
+
+ [WinFormsFact]
+ public void ListViewItemAccessibleObject_Ctor_Default()
+ {
+ using ListView list = new ListView();
+ ListViewItem listItem = new ListViewItem();
+ list.Items.Add(listItem);
+
+ AccessibleObject accessibleObject = listItem.AccessibilityObject;
+ Assert.True(list.IsHandleCreated);
+ Assert.NotNull(accessibleObject);
+ Assert.Equal(AccessibleRole.ListItem, accessibleObject.Role);
+ }
+
+ [WinFormsFact]
+ public void ListViewItemAccessibleObject_GetPropertyValue_returns_correct_values()
+ {
+ using var list = new ListView();
+ ListViewItem listItem = new ListViewItem("ListItem");
+ list.Items.Add(listItem);
+ AccessibleObject listItemAccessibleObject = listItem.AccessibilityObject;
+ Assert.True(list.IsHandleCreated);
+
+ object accessibleName = listItemAccessibleObject.GetPropertyValue(UiaCore.UIA.NamePropertyId);
+ Assert.Equal("ListItem", accessibleName);
+
+ object automationId = listItemAccessibleObject.GetPropertyValue(UiaCore.UIA.AutomationIdPropertyId);
+ Assert.Equal("ListViewItem-0", automationId);
+
+ object controlType = listItemAccessibleObject.GetPropertyValue(UiaCore.UIA.ControlTypePropertyId);
+ UiaCore.UIA expected = UiaCore.UIA.ListItemControlTypeId;
+ Assert.Equal(expected, controlType);
+
+ Assert.True((bool)listItemAccessibleObject.GetPropertyValue(UiaCore.UIA.IsSelectionItemPatternAvailablePropertyId));
+ Assert.True((bool)listItemAccessibleObject.GetPropertyValue(UiaCore.UIA.IsScrollItemPatternAvailablePropertyId));
+ Assert.True((bool)listItemAccessibleObject.GetPropertyValue(UiaCore.UIA.IsInvokePatternAvailablePropertyId));
+ }
+
+ [WinFormsFact]
+ public void ListViewItemAccessibleObject_ListWithTwoItems_FragmentNavigateWorkCorrectly()
+ {
+ using ListView listView = new ListView();
+ ListViewItem listItem1 = new ListViewItem(new string[] {
+ "Test A",
+ "Alpha"}, -1);
+ ListViewItem listItem2 = new ListViewItem(new string[] {
+ "Test B",
+ "Beta"}, -1);
+ ListViewItem listItem3 = new ListViewItem(new string[] {
+ "Test C",
+ "Gamma"}, -1);
+ listView.Items.Add(listItem1);
+ listView.Items.Add(listItem2);
+ listView.Items.Add(listItem3);
+ AccessibleObject accessibleObject1 = listItem1.AccessibilityObject;
+ AccessibleObject accessibleObject2 = listItem2.AccessibilityObject;
+ AccessibleObject accessibleObject3 = listItem3.AccessibilityObject;
+ Assert.True(listView.IsHandleCreated);
+
+ // First list view item
+ Assert.Null(accessibleObject1.FragmentNavigate(UiaCore.NavigateDirection.PreviousSibling));
+ UiaCore.IRawElementProviderFragment listItem1NextSibling = accessibleObject1.FragmentNavigate(UiaCore.NavigateDirection.NextSibling);
+ Assert.IsType(listItem1NextSibling);
+ Assert.Equal(accessibleObject2, listItem1NextSibling);
+
+ // Second list view item
+ UiaCore.IRawElementProviderFragment listItem2PreviousSibling = accessibleObject2.FragmentNavigate(UiaCore.NavigateDirection.PreviousSibling);
+ UiaCore.IRawElementProviderFragment listItem2NextSibling = accessibleObject2.FragmentNavigate(UiaCore.NavigateDirection.NextSibling);
+ Assert.IsType(listItem2PreviousSibling);
+ Assert.IsType(listItem2NextSibling);
+ Assert.Equal(accessibleObject1, listItem2PreviousSibling);
+ Assert.Equal(accessibleObject3, listItem2NextSibling);
+
+ // Third list view item
+ Assert.Null(accessibleObject3.FragmentNavigate(UiaCore.NavigateDirection.NextSibling));
+ UiaCore.IRawElementProviderFragment listItem3PreviousSibling = accessibleObject3.FragmentNavigate(UiaCore.NavigateDirection.PreviousSibling);
+ Assert.IsType(listItem3PreviousSibling);
+ Assert.Equal(accessibleObject2, listItem3PreviousSibling);
+
+ // Parent
+ Assert.Equal(accessibleObject1.FragmentNavigate(UiaCore.NavigateDirection.Parent), listView.AccessibilityObject);
+ Assert.Equal(accessibleObject2.FragmentNavigate(UiaCore.NavigateDirection.Parent), listView.AccessibilityObject);
+ Assert.Equal(accessibleObject3.FragmentNavigate(UiaCore.NavigateDirection.Parent), listView.AccessibilityObject);
+
+ // Childs
+ AccessibleObject firstChild = accessibleObject1.FragmentNavigate(UiaCore.NavigateDirection.FirstChild) as AccessibleObject;
+ AccessibleObject lastChild = accessibleObject1.FragmentNavigate(UiaCore.NavigateDirection.LastChild) as AccessibleObject;
+ Assert.IsType(firstChild);
+ Assert.IsType(lastChild);
+ Assert.NotEqual(firstChild, lastChild);
+ }
+ }
+}
diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ListViewItem.ListViewSubItem.ListViewSubItemAccessibleObjectTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ListViewItem.ListViewSubItem.ListViewSubItemAccessibleObjectTests.cs
new file mode 100644
index 00000000000..365bbdcb4d9
--- /dev/null
+++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ListViewItem.ListViewSubItem.ListViewSubItemAccessibleObjectTests.cs
@@ -0,0 +1,233 @@
+// 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 Xunit;
+using static System.Windows.Forms.ListViewItem.ListViewSubItem;
+using static Interop;
+
+namespace System.Windows.Forms.Tests
+{
+ public class ListViewItem_ListViewSubItem_ListViewSubItemAccessibleObjectTests
+ {
+ [WinFormsFact]
+ public void ListViewSubItemAccessibleObject_GetChild_ReturnCorrectValue()
+ {
+ using ListView list = new ListView();
+ ListViewItem listViewItem1 = new ListViewItem(new string[] {
+ "Test 1",
+ "Item 1",
+ "Something 1"}, -1);
+
+ ColumnHeader columnHeader1 = new ColumnHeader();
+ ColumnHeader columnHeader2 = new ColumnHeader();
+ ColumnHeader columnHeader3 = new ColumnHeader();
+
+ list.Columns.AddRange(new ColumnHeader[] {
+ columnHeader1,
+ columnHeader2,
+ columnHeader3});
+ list.HideSelection = false;
+ list.Items.Add(listViewItem1);
+ list.View = View.Details;
+
+ AccessibleObject accessibleObject = listViewItem1.AccessibilityObject.GetChild(0);
+ Assert.True(list.IsHandleCreated);
+ Assert.NotNull(accessibleObject);
+ Assert.IsType(accessibleObject);
+ }
+
+ [WinFormsFact]
+ public void ListViewSubItemAccessibleObject_GetPropertyValue_returns_correct_values()
+ {
+ using ListView list = new ListView();
+ ListViewItem listViewItem1 = new ListViewItem(new string[] {
+ "Test 1",
+ "Item 1",
+ "Something 1"}, -1);
+
+ ColumnHeader columnHeader1 = new ColumnHeader();
+ ColumnHeader columnHeader2 = new ColumnHeader();
+ ColumnHeader columnHeader3 = new ColumnHeader();
+
+ list.Columns.AddRange(new ColumnHeader[] {
+ columnHeader1,
+ columnHeader2,
+ columnHeader3});
+ list.HideSelection = false;
+ list.Items.Add(listViewItem1);
+ list.View = View.Details;
+
+ AccessibleObject accessibleObject = listViewItem1.AccessibilityObject.GetChild(0);
+ Assert.True(list.IsHandleCreated);
+
+ object accessibleName = accessibleObject.GetPropertyValue(UiaCore.UIA.NamePropertyId);
+ Assert.Equal("Test 1", accessibleName);
+
+ object automationId = accessibleObject.GetPropertyValue(UiaCore.UIA.AutomationIdPropertyId);
+ Assert.Equal("ListViewSubItem-0", automationId);
+
+ object frameworkId = accessibleObject.GetPropertyValue(UiaCore.UIA.FrameworkIdPropertyId);
+ Assert.Equal("WinForm", frameworkId);
+
+ object controlType = accessibleObject.GetPropertyValue(UiaCore.UIA.ControlTypePropertyId);
+ UiaCore.UIA expected = UiaCore.UIA.TextControlTypeId;
+ Assert.Equal(expected, controlType);
+
+ Assert.True((bool)accessibleObject.GetPropertyValue(UiaCore.UIA.IsGridItemPatternAvailablePropertyId));
+ Assert.True((bool)accessibleObject.GetPropertyValue(UiaCore.UIA.IsTableItemPatternAvailablePropertyId));
+ }
+
+ [WinFormsFact]
+ public void ListViewSubItemAccessibleObject_FragmentNavigate_WorkCorrectly()
+ {
+ using ListView list = new ListView();
+ ListViewItem listViewItem1 = new ListViewItem(new string[] {
+ "Test 1",
+ "Item 1",
+ "Something 1"}, -1);
+
+ ColumnHeader columnHeader1 = new ColumnHeader();
+ ColumnHeader columnHeader2 = new ColumnHeader();
+ ColumnHeader columnHeader3 = new ColumnHeader();
+
+ list.Columns.AddRange(new ColumnHeader[] {
+ columnHeader1,
+ columnHeader2,
+ columnHeader3});
+ list.HideSelection = false;
+ list.Items.Add(listViewItem1);
+ list.View = View.Details;
+
+ AccessibleObject subItemAccObj1 = listViewItem1.AccessibilityObject.GetChild(0);
+ AccessibleObject subItemAccObj2 = listViewItem1.AccessibilityObject.GetChild(1);
+ AccessibleObject subItemAccObj3 = listViewItem1.AccessibilityObject.GetChild(2);
+ Assert.True(list.IsHandleCreated);
+
+ Assert.Null(subItemAccObj1.FragmentNavigate(UiaCore.NavigateDirection.PreviousSibling));
+ UiaCore.IRawElementProviderFragment subItem1NextSibling = subItemAccObj1.FragmentNavigate(UiaCore.NavigateDirection.NextSibling);
+ Assert.IsType(subItem1NextSibling);
+ Assert.NotNull(subItem1NextSibling);
+
+ UiaCore.IRawElementProviderFragment subItem2NextSibling = subItemAccObj2.FragmentNavigate(UiaCore.NavigateDirection.NextSibling);
+ UiaCore.IRawElementProviderFragment subItem2PreviousSibling = subItemAccObj2.FragmentNavigate(UiaCore.NavigateDirection.PreviousSibling);
+ Assert.IsType(subItem2NextSibling);
+ Assert.IsType(subItem2PreviousSibling);
+ Assert.NotNull(subItem2NextSibling);
+ Assert.NotNull(subItem2PreviousSibling);
+
+ Assert.Null(subItemAccObj3.FragmentNavigate(UiaCore.NavigateDirection.NextSibling));
+ UiaCore.IRawElementProviderFragment subItem3PreviousSibling = subItemAccObj3.FragmentNavigate(UiaCore.NavigateDirection.PreviousSibling);
+ Assert.IsType(subItem3PreviousSibling);
+ Assert.NotNull(subItem3PreviousSibling);
+
+ // Parent
+ Assert.Equal(subItemAccObj1.FragmentNavigate(UiaCore.NavigateDirection.Parent), listViewItem1.AccessibilityObject);
+ Assert.Equal(subItemAccObj2.FragmentNavigate(UiaCore.NavigateDirection.Parent), listViewItem1.AccessibilityObject);
+ Assert.Equal(subItemAccObj3.FragmentNavigate(UiaCore.NavigateDirection.Parent), listViewItem1.AccessibilityObject);
+ }
+
+ [WinFormsFact]
+ public void ListViewSubItemAccessibleObject_Bounds_ReturnCorrectValue()
+ {
+ using ListView list = new ListView();
+ ListViewItem listViewItem1 = new ListViewItem(new string[] {
+ "Test 1",
+ "Item 1",
+ "Something 1"}, -1);
+
+ ColumnHeader columnHeader1 = new ColumnHeader();
+ ColumnHeader columnHeader2 = new ColumnHeader();
+ ColumnHeader columnHeader3 = new ColumnHeader();
+
+ list.Columns.AddRange(new ColumnHeader[] {
+ columnHeader1,
+ columnHeader2,
+ columnHeader3});
+ list.HideSelection = false;
+ list.Items.Add(listViewItem1);
+ list.View = View.Details;
+
+ ListViewItem.ListViewSubItem subItem = listViewItem1.SubItems[0];
+ AccessibleObject accessibleObject = listViewItem1.AccessibilityObject.GetChild(0);
+ Assert.True(list.IsHandleCreated);
+
+ int actualWidth = accessibleObject.Bounds.Width;
+ int expectedWidth = listViewItem1.SubItems[1].Bounds.X - subItem.Bounds.X;
+ Assert.Equal(expectedWidth, actualWidth);
+
+ int actualHeight = accessibleObject.Bounds.Height;
+ int expectedHeight = subItem.Bounds.Height;
+ Assert.Equal(expectedHeight, actualHeight);
+
+ Rectangle actualBounds = accessibleObject.Bounds;
+ actualBounds.Location = new Point(0, 0);
+ Rectangle expectedBounds = new Rectangle(subItem.Bounds.X, subItem.Bounds.Y, expectedWidth, expectedHeight);
+ expectedBounds.Location = new Point(0, 0);
+ Assert.Equal(expectedBounds, actualBounds);
+ }
+
+ [WinFormsFact]
+ public void ListViewSubItemAccessibleObject_ColunmProperty_ReturnCorrectValue()
+ {
+ using ListView list = new ListView();
+ ListViewItem listViewItem1 = new ListViewItem(new string[] {
+ "Test 1",
+ "Item 1",
+ "Something 1"}, -1);
+
+ ColumnHeader columnHeader1 = new ColumnHeader();
+ ColumnHeader columnHeader2 = new ColumnHeader();
+ ColumnHeader columnHeader3 = new ColumnHeader();
+
+ list.Columns.AddRange(new ColumnHeader[] {
+ columnHeader1,
+ columnHeader2,
+ columnHeader3});
+ list.HideSelection = false;
+ list.Items.Add(listViewItem1);
+ list.View = View.Details;
+
+ AccessibleObject subItemAccObj1 = listViewItem1.AccessibilityObject.GetChild(0);
+ AccessibleObject subItemAccObj2 = listViewItem1.AccessibilityObject.GetChild(1);
+ AccessibleObject subItemAccObj3 = listViewItem1.AccessibilityObject.GetChild(2);
+ Assert.True(list.IsHandleCreated);
+
+ Assert.Equal(0, subItemAccObj1.Column);
+ Assert.Equal(1, subItemAccObj2.Column);
+ Assert.Equal(2, subItemAccObj3.Column);
+ }
+
+ [WinFormsFact]
+ public void ListViewSubItemAccessibleObject_RowProperty_ReturnCorrectValue()
+ {
+ using ListView list = new ListView();
+ ListViewItem listViewItem1 = new ListViewItem(new string[] {
+ "Test 1",
+ "Item 1",
+ "Something 1"}, -1);
+
+ ColumnHeader columnHeader1 = new ColumnHeader();
+ ColumnHeader columnHeader2 = new ColumnHeader();
+ ColumnHeader columnHeader3 = new ColumnHeader();
+
+ list.Columns.AddRange(new ColumnHeader[] {
+ columnHeader1,
+ columnHeader2,
+ columnHeader3});
+ list.HideSelection = false;
+ list.Items.Add(listViewItem1);
+ list.View = View.Details;
+
+ AccessibleObject subItemAccObj1 = listViewItem1.AccessibilityObject.GetChild(0);
+ AccessibleObject subItemAccObj2 = listViewItem1.AccessibilityObject.GetChild(1);
+ AccessibleObject subItemAccObj3 = listViewItem1.AccessibilityObject.GetChild(2);
+ Assert.True(list.IsHandleCreated);
+
+ Assert.Equal(0, subItemAccObj1.Row);
+ Assert.Equal(0, subItemAccObj2.Row);
+ Assert.Equal(0, subItemAccObj3.Row);
+ }
+ }
+}