From f379253b8c34640aa252d039f16e498f6b7a933e Mon Sep 17 00:00:00 2001 From: Sergey Smirnov Date: Fri, 16 Apr 2021 03:20:04 +0300 Subject: [PATCH] Fix Empty ListViewGroups are displayed in the Inspect tree #4779 (#4789) In this issue, we have two problems with invisible ListViewGroups and invisible ListViewItems. An ListViewItem is considered invisible when it is in a ListViewGroup but not added to the ListView. In this case, the ListViewGroup contains data about it, but the ListViewItem is not displayed in the list. To solve this problem, the "GetVisibleItems" method was added, which returns a list of only displayed ListViewItems, in which the property "ListView" is not empty. Now, when receiving data about the ListViewItems of a ListViewGroup, we use this method and not "Items" property of the ListViewGroup. The second issue, if the ListViewGroup is empty or contains invisible ListViewItems (case above), then this ListViewGroup is also not displayed in the ListView. This issue was solved in the same way as the issue above, by adding the "GetVisibleGroups" method that returns a list of only the displayed ListViewGroups. Added unit tests for ListViewAccessibleObject, ListViewGroupAccessibleObject, ListViewItemAccessibleObject --- .../ListView.ListViewAccessibleObject.cs | 40 +- ...ViewGroup.ListViewGroupAccessibleObject.cs | 39 +- .../ListVIew.ListViewAccessibleObjectTests.cs | 301 +++++++++++++++ ...roup.ListViewGroupAccessibleObjectTests.cs | 348 ++++++++++++++++++ ...wItem.ListViewItemAccessibleObjectTests.cs | 107 ++++++ 5 files changed, 822 insertions(+), 13 deletions(-) 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 index c54c09fdaaa..d50aca2789b 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/ListView.ListViewAccessibleObject.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/ListView.ListViewAccessibleObject.cs @@ -2,6 +2,7 @@ // 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; @@ -108,7 +109,13 @@ internal override int[]? RuntimeId public override AccessibleObject? GetChild(int index) { - if (!_owningListView.IsHandleCreated || index < 0 || index >= GetChildCount()) + if (!_owningListView.IsHandleCreated || index < 0) + { + return null; + } + + IReadOnlyList visibleGroups = GetVisibleGroups(); + if (index >= GetChildCount(visibleGroups)) { return null; } @@ -120,7 +127,7 @@ internal override int[]? RuntimeId if (!OwnerHasDefaultGroup) { - return _owningListView.Groups[index].AccessibilityObject; + return visibleGroups[index].AccessibilityObject; } // Default group has the last index out of the Groups.Count @@ -129,7 +136,7 @@ internal override int[]? RuntimeId // default group is the first before other groups. return index == 0 ? _owningListView.DefaultGroup.AccessibilityObject - : _owningListView.Groups[index - 1].AccessibilityObject; + : visibleGroups[index - 1].AccessibilityObject; } public override int GetChildCount() @@ -139,9 +146,14 @@ public override int GetChildCount() return 0; } + return GetChildCount(GetVisibleGroups()); + } + + private int GetChildCount(IReadOnlyList visibleGroups) + { if (ShowGroupAccessibleObject) { - return OwnerHasDefaultGroup ? _owningListView.Groups.Count + 1 : _owningListView.Groups.Count; + return OwnerHasDefaultGroup ? visibleGroups.Count + 1 : visibleGroups.Count; } return _owningListView.Items.Count; @@ -282,6 +294,26 @@ internal override UiaCore.IRawElementProviderSimple[] GetSelection() return selectedItemProviders; } + internal IReadOnlyList GetVisibleGroups() + { + List list = new(); + if (!ShowGroupAccessibleObject) + { + return list; + } + + foreach (ListViewGroup listViewGroup in _owningListView.Groups) + { + if (listViewGroup.AccessibilityObject is ListViewGroup.ListViewGroupAccessibleObject listViewGroupAccessibleObject + && listViewGroupAccessibleObject.GetVisibleItems().Count > 0) + { + list.Add(listViewGroup); + } + } + + return list; + } + public override AccessibleObject? HitTest(int x, int y) { if (!_owningListView.IsHandleCreated) 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 index f22b9bc71b7..b7acce3b000 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/ListViewGroup.ListViewGroupAccessibleObject.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/ListViewGroup.ListViewGroupAccessibleObject.cs @@ -2,7 +2,9 @@ // 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 System.Windows.Forms.ListView; using static Interop; using static Interop.ComCtl32; @@ -13,6 +15,7 @@ public partial class ListViewGroup internal class ListViewGroupAccessibleObject : AccessibleObject { private readonly ListView _owningListView; + private readonly ListViewAccessibleObject _owningListViewAccessibilityObject; private readonly ListViewGroup _owningGroup; private readonly bool _owningGroupIsDefault; @@ -26,6 +29,9 @@ public ListViewGroupAccessibleObject(ListViewGroup owningGroup, bool owningGroup ? _owningGroup.Items[0].ListView : throw new InvalidOperationException(nameof(owningGroup.ListView))); + _owningListViewAccessibilityObject = _owningListView.AccessibilityObject as ListView.ListViewAccessibleObject + ?? throw new InvalidOperationException(nameof(_owningListView.AccessibilityObject)); + _owningGroupIsDefault = owningGroupIsDefault; } @@ -45,8 +51,8 @@ public override Rectangle Bounds 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, + _owningListViewAccessibilityObject.Bounds.X + groupRect.left, + _owningListViewAccessibilityObject.Bounds.Y + groupRect.top, groupRect.right - groupRect.left, groupRect.bottom - groupRect.top); } @@ -77,7 +83,7 @@ internal override int[]? RuntimeId { get { - var owningListViewRuntimeId = _owningListView.AccessibilityObject.RuntimeId; + var owningListViewRuntimeId = _owningListViewAccessibilityObject.RuntimeId; if (owningListViewRuntimeId is null) { return base.RuntimeId; @@ -148,6 +154,20 @@ private bool GetNativeFocus() _ => base.GetPropertyValue(propertyID) }; + internal IReadOnlyList GetVisibleItems() + { + List visibleItems = new(); + foreach (ListViewItem listViewItem in _owningGroup.Items) + { + if (listViewItem.ListView is not null) + { + visibleItems.Add(listViewItem); + } + } + + return visibleItems; + } + internal override UiaCore.IRawElementProviderFragment? FragmentNavigate(UiaCore.NavigateDirection direction) { if (!_owningListView.IsHandleCreated || _owningListView.VirtualMode) @@ -158,11 +178,11 @@ private bool GetNativeFocus() switch (direction) { case UiaCore.NavigateDirection.Parent: - return _owningListView.AccessibilityObject; + return _owningListViewAccessibilityObject; case UiaCore.NavigateDirection.NextSibling: - return (_owningListView.AccessibilityObject as ListView.ListViewAccessibleObject)?.GetNextChild(this); + return _owningListViewAccessibilityObject.GetNextChild(this); case UiaCore.NavigateDirection.PreviousSibling: - return (_owningListView.AccessibilityObject as ListView.ListViewAccessibleObject)?.GetPreviousChild(this); + return _owningListViewAccessibilityObject.GetPreviousChild(this); case UiaCore.NavigateDirection.FirstChild: int childCount = GetChildCount(); if (childCount > 0) @@ -195,12 +215,13 @@ private bool GetNativeFocus() if (!_owningGroupIsDefault) { - if (index < 0 || index >= _owningGroup.Items.Count) + IReadOnlyList visibleItems = GetVisibleItems(); + if (index < 0 || index >= visibleItems.Count) { return null; } - return _owningGroup.Items[index].AccessibilityObject; + return visibleItems[index].AccessibilityObject; } foreach (ListViewItem? item in _owningListView.Items) @@ -294,7 +315,7 @@ public override int GetChildCount() } else { - return _owningGroup.Items.Count; + return GetVisibleItems().Count; } } 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 index d8f87d45247..85e21241ad7 100644 --- 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 @@ -760,6 +760,307 @@ public void ListViewAccessibleObject_GetPropertyValue_ControlType_IsExpected_For Assert.Equal(expected, actual); Assert.False(listView.IsHandleCreated); } + + [WinFormsTheory] + [InlineData(View.Details)] + [InlineData(View.LargeIcon)] + [InlineData(View.SmallIcon)] + [InlineData(View.Tile)] + public void ListViewAccessibleObject_FragmentNavigate_ReturnExpected_InvisibleGroups(View view) + { + if (!Application.UseVisualStyles) + { + return; + } + + using ListView listView = GetListViewItemWithEmptyGroups(view); + ListViewGroup listViewGroupWithItems1 = listView.Groups[1]; + ListViewGroup listViewGroupWithItems2 = listView.Groups[2]; + AccessibleObject accessibleObject = listView.AccessibilityObject; + + Assert.Equal(listViewGroupWithItems1.AccessibilityObject, + accessibleObject.FragmentNavigate(UiaCore.NavigateDirection.FirstChild)); + + Assert.Equal(listViewGroupWithItems2.AccessibilityObject, + accessibleObject.FragmentNavigate(UiaCore.NavigateDirection.LastChild)); + + Assert.True(listView.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(View.Details)] + [InlineData(View.LargeIcon)] + [InlineData(View.SmallIcon)] + [InlineData(View.Tile)] + public void ListViewAccessibleObject_FragmentNavigate_ReturnExpected_InvisibleGroups_AfterAddingItems(View view) + { + if (!Application.UseVisualStyles) + { + return; + } + + using ListView listView = GetListViewItemWithEmptyGroups(view); + AccessibleObject accessibleObject = listView.AccessibilityObject; + + Assert.Equal(listView.Groups[1].AccessibilityObject, + accessibleObject.FragmentNavigate(UiaCore.NavigateDirection.FirstChild)); + + Assert.Equal(listView.Groups[2].AccessibilityObject, + accessibleObject.FragmentNavigate(UiaCore.NavigateDirection.LastChild)); + + ListViewItem listViewItem1 = new(); + ListViewItem listViewItem2 = new(); + listView.Items.Add(listViewItem1); + listView.Items.Add(listViewItem2); + listView.Groups[0].Items.Add(listViewItem1); + listView.Groups[3].Items.Add(listViewItem2); + + Assert.Equal(listView.Groups[0].AccessibilityObject, + accessibleObject.FragmentNavigate(UiaCore.NavigateDirection.FirstChild)); + + Assert.Equal(listView.Groups[3].AccessibilityObject, + accessibleObject.FragmentNavigate(UiaCore.NavigateDirection.LastChild)); + + Assert.True(listView.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(View.Details)] + [InlineData(View.LargeIcon)] + [InlineData(View.SmallIcon)] + [InlineData(View.Tile)] + public void ListViewAccessibleObject_FragmentNavigate_ReturnExpected_InvisibleGroups_AfterRemovingItems(View view) + { + if (!Application.UseVisualStyles) + { + return; + } + + using ListView listView = GetListViewItemWithEmptyGroups(view); + AccessibleObject accessibleObject = listView.AccessibilityObject; + + Assert.Equal(listView.Groups[1].AccessibilityObject, + accessibleObject.FragmentNavigate(UiaCore.NavigateDirection.FirstChild)); + + Assert.Equal(listView.Groups[2].AccessibilityObject, + accessibleObject.FragmentNavigate(UiaCore.NavigateDirection.LastChild)); + + listView.Groups[1].Items.RemoveAt(0); + listView.Groups[2].Items.RemoveAt(0); + + Assert.Equal(listView.DefaultGroup.AccessibilityObject, + accessibleObject.FragmentNavigate(UiaCore.NavigateDirection.FirstChild)); + + Assert.Equal(listView.DefaultGroup.AccessibilityObject, + accessibleObject.FragmentNavigate(UiaCore.NavigateDirection.LastChild)); + + Assert.True(listView.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(View.Details)] + [InlineData(View.LargeIcon)] + [InlineData(View.SmallIcon)] + [InlineData(View.Tile)] + public void ListViewAccessibleObject_GetChildCount_ReturnExpected_InvisibleGroups(View view) + { + if (!Application.UseVisualStyles) + { + return; + } + + using ListView listView = GetListViewItemWithEmptyGroups(view); + AccessibleObject accessibleObject = listView.AccessibilityObject; + + Assert.Equal(2, accessibleObject.GetChildCount()); + Assert.True(listView.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(View.Details)] + [InlineData(View.LargeIcon)] + [InlineData(View.SmallIcon)] + [InlineData(View.Tile)] + public void ListViewAccessibleObject_GetChildCount_ReturnExpected_InvisibleGroups_AfterAddingItems(View view) + { + if (!Application.UseVisualStyles) + { + return; + } + + using ListView listView = GetListViewItemWithEmptyGroups(view); + AccessibleObject accessibleObject = listView.AccessibilityObject; + + Assert.Equal(2, accessibleObject.GetChildCount()); + + ListViewItem listViewItem1 = new(); + ListViewItem listViewItem2 = new(); + listView.Items.Add(listViewItem1); + listView.Items.Add(listViewItem2); + listView.Groups[0].Items.Add(listViewItem1); + listView.Groups[3].Items.Add(listViewItem2); + + Assert.Equal(4, accessibleObject.GetChildCount()); + Assert.True(listView.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(View.Details)] + [InlineData(View.LargeIcon)] + [InlineData(View.SmallIcon)] + [InlineData(View.Tile)] + public void ListViewAccessibleObject_GetChildCount_ReturnExpected_InvisibleGroups_AfterRemovingItems(View view) + { + if (!Application.UseVisualStyles) + { + return; + } + + using ListView listView = GetListViewItemWithEmptyGroups(view); + AccessibleObject accessibleObject = listView.AccessibilityObject; + + Assert.Equal(2, accessibleObject.GetChildCount()); + + listView.Groups[1].Items.RemoveAt(0); + listView.Groups[2].Items.RemoveAt(0); + + Assert.Equal(1, accessibleObject.GetChildCount()); + Assert.True(listView.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(View.Details)] + [InlineData(View.LargeIcon)] + [InlineData(View.SmallIcon)] + [InlineData(View.Tile)] + public void ListViewAccessibleObject_GetChildCount_ReturnExpected_GroupWithInvalidAccessibleObject(View view) + { + if (!Application.UseVisualStyles) + { + return; + } + + using ListView listView = GetListViewItemWithEmptyGroups(view); + AccessibleObject accessibleObject = listView.AccessibilityObject; + Assert.Equal(2, accessibleObject.GetChildCount()); + + listView.Groups[1].TestAccessor().Dynamic._accessibilityObject = new AccessibleObject(); + + Assert.Equal(1, accessibleObject.GetChildCount()); + Assert.True(listView.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(View.Details)] + [InlineData(View.LargeIcon)] + [InlineData(View.SmallIcon)] + [InlineData(View.Tile)] + public void ListViewAccessibleObject_GetChild_ReturnExpected_InvisibleGroups(View view) + { + if (!Application.UseVisualStyles) + { + return; + } + + using ListView listView = GetListViewItemWithEmptyGroups(view); + ListViewGroup listViewGroupWithItems1 = listView.Groups[1]; + ListViewGroup listViewGroupWithItems2 = listView.Groups[2]; + AccessibleObject accessibleObject = listView.AccessibilityObject; + + Assert.Equal(listViewGroupWithItems1.AccessibilityObject, accessibleObject.GetChild(0)); + Assert.Equal(listViewGroupWithItems2.AccessibilityObject, accessibleObject.GetChild(1)); + Assert.Null(accessibleObject.GetChild(2)); + Assert.Null(accessibleObject.GetChild(3)); + Assert.True(listView.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(View.Details)] + [InlineData(View.LargeIcon)] + [InlineData(View.SmallIcon)] + [InlineData(View.Tile)] + public void ListViewAccessibleObject_GetChild_ReturnExpected_InvisibleGroups_AfterAddingItems(View view) + { + if (!Application.UseVisualStyles) + { + return; + } + + using ListView listView = GetListViewItemWithEmptyGroups(view); + AccessibleObject accessibleObject = listView.AccessibilityObject; + + Assert.Equal(listView.Groups[1].AccessibilityObject, accessibleObject.GetChild(0)); + Assert.Equal(listView.Groups[2].AccessibilityObject, accessibleObject.GetChild(1)); + Assert.Null(accessibleObject.GetChild(2)); + Assert.Null(accessibleObject.GetChild(3)); + + ListViewItem listViewItem1 = new(); + ListViewItem listViewItem2 = new(); + listView.Items.Add(listViewItem1); + listView.Items.Add(listViewItem2); + listView.Groups[0].Items.Add(listViewItem1); + listView.Groups[3].Items.Add(listViewItem2); + + Assert.Equal(listView.Groups[0].AccessibilityObject, accessibleObject.GetChild(0)); + Assert.Equal(listView.Groups[1].AccessibilityObject, accessibleObject.GetChild(1)); + Assert.Equal(listView.Groups[2].AccessibilityObject, accessibleObject.GetChild(2)); + Assert.Equal(listView.Groups[3].AccessibilityObject, accessibleObject.GetChild(3)); + Assert.True(listView.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(View.Details)] + [InlineData(View.LargeIcon)] + [InlineData(View.SmallIcon)] + [InlineData(View.Tile)] + public void ListViewAccessibleObject_GetChild_ReturnExpected_InvisibleGroups_AfterRemovingItems(View view) + { + if (!Application.UseVisualStyles) + { + return; + } + + using ListView listView = GetListViewItemWithEmptyGroups(view); + AccessibleObject accessibleObject = listView.AccessibilityObject; + + Assert.Equal(listView.Groups[1].AccessibilityObject, accessibleObject.GetChild(0)); + Assert.Equal(listView.Groups[2].AccessibilityObject, accessibleObject.GetChild(1)); + Assert.Null(accessibleObject.GetChild(2)); + Assert.Null(accessibleObject.GetChild(3)); + + listView.Groups[1].Items.RemoveAt(0); + listView.Groups[2].Items.RemoveAt(0); + + Assert.Equal(listView.DefaultGroup.AccessibilityObject, accessibleObject.GetChild(0)); + Assert.Null(accessibleObject.GetChild(1)); + Assert.Null(accessibleObject.GetChild(2)); + Assert.Null(accessibleObject.GetChild(3)); + Assert.True(listView.IsHandleCreated); + } + + private ListView GetListViewItemWithEmptyGroups(View view) + { + ListView listView = new ListView() { View = view}; + listView.CreateControl(); + ListViewGroup listViewGroupWithoutItems = new("Group without items"); + ListViewGroup listViewGroupWithItems1 = new("Group with item 1"); + ListViewGroup listViewGroupWithItems2 = new("Group with item 2"); + ListViewGroup listViewGroupWithInvisibleItems = new("Group with invisible item"); + listView.Groups.Add(listViewGroupWithoutItems); + listView.Groups.Add(listViewGroupWithItems1); + listView.Groups.Add(listViewGroupWithItems2); + listView.Groups.Add(listViewGroupWithInvisibleItems); + ListViewItem listViewItem1 = new(); + ListViewItem listViewItem2 = new(); + ListViewItem listViewItem3 = new(); + listView.Items.Add(listViewItem1); + listView.Items.Add(listViewItem2); + listViewGroupWithItems1.Items.Add(listViewItem1); + listViewGroupWithItems2.Items.Add(listViewItem2); + listViewGroupWithInvisibleItems.Items.Add(listViewItem3); + + return listView; + } } } 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 index 13ee620bfe6..73b5d465ee1 100644 --- 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 @@ -640,5 +640,353 @@ public void ListViewGroupAccessibleObject_ExpandCollapseState_ReturnExpected(Vie Assert.Equal(ExpandCollapseState.Expanded, listView.DefaultGroup.AccessibilityObject.ExpandCollapseState); Assert.Equal(createHandle, listView.IsHandleCreated); } + + [WinFormsTheory] + [InlineData(View.Details)] + [InlineData(View.LargeIcon)] + [InlineData(View.SmallIcon)] + [InlineData(View.Tile)] + public void ListViewAccessibleObject_FragmentNaviage_Sibling_ReturnsExpected_InvisibleGroups(View view) + { + if (!Application.UseVisualStyles) + { + return; + } + + using ListView listView = GetListViewItemWithEmptyGroups(view); + AccessibleObject accessibleObject = listView.AccessibilityObject; + AccessibleObject listViewGroupWithItems1 = listView.Groups[1].AccessibilityObject; + AccessibleObject listViewGroupWithItems2 = listView.Groups[2].AccessibilityObject; + + Assert.Null(listViewGroupWithItems1.FragmentNavigate(NavigateDirection.PreviousSibling)); + Assert.Equal(listViewGroupWithItems2, listViewGroupWithItems1.FragmentNavigate(NavigateDirection.NextSibling)); + Assert.Equal(listViewGroupWithItems1, listViewGroupWithItems2.FragmentNavigate(NavigateDirection.PreviousSibling)); + Assert.Null(listViewGroupWithItems2.FragmentNavigate(NavigateDirection.NextSibling)); + Assert.True(listView.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(View.Details)] + [InlineData(View.LargeIcon)] + [InlineData(View.SmallIcon)] + [InlineData(View.Tile)] + public void ListViewAccessibleObject_FragmentNaviage_ReturnsExpected_Sibling_InvisibleGroups_AfterAddingItems(View view) + { + if (!Application.UseVisualStyles) + { + return; + } + + using ListView listView = GetListViewItemWithEmptyGroups(view); + AccessibleObject accessibleObject = listView.AccessibilityObject; + + Assert.Null(GetAccessibleObject(1).FragmentNavigate(NavigateDirection.PreviousSibling)); + Assert.Equal(GetAccessibleObject(2), GetAccessibleObject(1).FragmentNavigate(NavigateDirection.NextSibling)); + Assert.Equal(GetAccessibleObject(1), GetAccessibleObject(2).FragmentNavigate(NavigateDirection.PreviousSibling)); + Assert.Null(GetAccessibleObject(2).FragmentNavigate(NavigateDirection.NextSibling)); + + ListViewItem listViewItem1 = new(); + ListViewItem listViewItem2 = new(); + listView.Items.Add(listViewItem1); + listView.Items.Add(listViewItem2); + listView.Groups[0].Items.Add(listViewItem1); + listView.Groups[3].Items.Add(listViewItem2); + + Assert.Null(GetAccessibleObject(0).FragmentNavigate(NavigateDirection.PreviousSibling)); + Assert.Equal(GetAccessibleObject(1), GetAccessibleObject(0).FragmentNavigate(NavigateDirection.NextSibling)); + Assert.Equal(GetAccessibleObject(0), GetAccessibleObject(1).FragmentNavigate(NavigateDirection.PreviousSibling)); + Assert.Equal(GetAccessibleObject(2), GetAccessibleObject(1).FragmentNavigate(NavigateDirection.NextSibling)); + Assert.Equal(GetAccessibleObject(1), GetAccessibleObject(2).FragmentNavigate(NavigateDirection.PreviousSibling)); + Assert.Equal(GetAccessibleObject(3), GetAccessibleObject(2).FragmentNavigate(NavigateDirection.NextSibling)); + Assert.Equal(GetAccessibleObject(2), GetAccessibleObject(3).FragmentNavigate(NavigateDirection.PreviousSibling)); + Assert.Null(GetAccessibleObject(3).FragmentNavigate(NavigateDirection.NextSibling)); + Assert.True(listView.IsHandleCreated); + + AccessibleObject GetAccessibleObject(int index) => listView.Groups[index].AccessibilityObject; + } + + [WinFormsTheory] + [InlineData(View.Details)] + [InlineData(View.LargeIcon)] + [InlineData(View.SmallIcon)] + [InlineData(View.Tile)] + public void ListViewAccessibleObject_FragmentNaviage_Sibling_ReturnsExpected_InvisibleGroups_AfterRemovingItems(View view) + { + if (!Application.UseVisualStyles) + { + return; + } + + using ListView listView = GetListViewItemWithEmptyGroups(view); + AccessibleObject accessibleObject = listView.AccessibilityObject; + AccessibleObject listViewGroupWithItems1 = listView.Groups[1].AccessibilityObject; + AccessibleObject listViewGroupWithItems2 = listView.Groups[2].AccessibilityObject; + + Assert.Null(listViewGroupWithItems1.FragmentNavigate(NavigateDirection.PreviousSibling)); + Assert.Equal(listViewGroupWithItems2, listViewGroupWithItems1.FragmentNavigate(NavigateDirection.NextSibling)); + Assert.Equal(listViewGroupWithItems1, listViewGroupWithItems2.FragmentNavigate(NavigateDirection.PreviousSibling)); + Assert.Null(listViewGroupWithItems2.FragmentNavigate(NavigateDirection.NextSibling)); + + listView.Groups[2].Items.RemoveAt(0); + + Assert.Equal(listView.DefaultGroup.AccessibilityObject, listViewGroupWithItems1.FragmentNavigate(NavigateDirection.PreviousSibling)); + Assert.Null(listViewGroupWithItems1.FragmentNavigate(NavigateDirection.NextSibling)); + Assert.True(listView.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(View.Details)] + [InlineData(View.LargeIcon)] + [InlineData(View.SmallIcon)] + [InlineData(View.Tile)] + public void ListViewAccessibleObject_FragmentNaviage_Child_ReturnsExpected_InvisibleItems(View view) + { + if (!Application.UseVisualStyles) + { + return; + } + + using ListView listView = GetListViewItemWithInvisibleItems(view); + AccessibleObject accessibleObject = listView.Groups[0].AccessibilityObject; + + Assert.Equal(listView.Groups[0].Items[1].AccessibilityObject, accessibleObject.FragmentNavigate(NavigateDirection.FirstChild)); + Assert.Equal(listView.Groups[0].Items[2].AccessibilityObject, accessibleObject.FragmentNavigate(NavigateDirection.LastChild)); + Assert.True(listView.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(View.Details)] + [InlineData(View.LargeIcon)] + [InlineData(View.SmallIcon)] + [InlineData(View.Tile)] + public void ListViewAccessibleObject_FragmentNaviage_Child_ReturnsExpected_InvisibleItems_AfterAddingItems(View view) + { + if (!Application.UseVisualStyles) + { + return; + } + + using ListView listView = GetListViewItemWithInvisibleItems(view); + AccessibleObject accessibleObject = listView.Groups[0].AccessibilityObject; + + Assert.Equal(listView.Groups[0].Items[1].AccessibilityObject, accessibleObject.FragmentNavigate(NavigateDirection.FirstChild)); + Assert.Equal(listView.Groups[0].Items[2].AccessibilityObject, accessibleObject.FragmentNavigate(NavigateDirection.LastChild)); + + listView.Items.Add(listView.Groups[0].Items[0]); + listView.Items.Add(listView.Groups[0].Items[3]); + + Assert.Equal(listView.Groups[0].Items[0].AccessibilityObject, accessibleObject.FragmentNavigate(NavigateDirection.FirstChild)); + Assert.Equal(listView.Groups[0].Items[3].AccessibilityObject, accessibleObject.FragmentNavigate(NavigateDirection.LastChild)); + Assert.True(listView.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(View.Details)] + [InlineData(View.LargeIcon)] + [InlineData(View.SmallIcon)] + [InlineData(View.Tile)] + public void ListViewAccessibleObject_FragmentNaviage_Child_ReturnsExpected_InvisibleItems_AfterRemovingItems(View view) + { + if (!Application.UseVisualStyles) + { + return; + } + + using ListView listView = GetListViewItemWithInvisibleItems(view); + AccessibleObject accessibleObject = listView.Groups[0].AccessibilityObject; + + Assert.Equal(listView.Groups[0].Items[1].AccessibilityObject, accessibleObject.FragmentNavigate(NavigateDirection.FirstChild)); + Assert.Equal(listView.Groups[0].Items[2].AccessibilityObject, accessibleObject.FragmentNavigate(NavigateDirection.LastChild)); + + listView.Items.RemoveAt(1); + + Assert.Equal(listView.Groups[0].Items[1].AccessibilityObject, accessibleObject.FragmentNavigate(NavigateDirection.FirstChild)); + Assert.Equal(listView.Groups[0].Items[1].AccessibilityObject, accessibleObject.FragmentNavigate(NavigateDirection.LastChild)); + Assert.True(listView.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(View.Details)] + [InlineData(View.LargeIcon)] + [InlineData(View.SmallIcon)] + [InlineData(View.Tile)] + public void ListViewAccessibleObject_GetChildCount_ReturnsExpected_InvisibleItems(View view) + { + if (!Application.UseVisualStyles) + { + return; + } + + using ListView listView = GetListViewItemWithInvisibleItems(view); + AccessibleObject accessibleObject = listView.Groups[0].AccessibilityObject; + + Assert.Equal(2, accessibleObject.GetChildCount()); + Assert.True(listView.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(View.Details)] + [InlineData(View.LargeIcon)] + [InlineData(View.SmallIcon)] + [InlineData(View.Tile)] + public void ListViewAccessibleObject_GetChildCount_ReturnsExpected_InvisibleItems_AfterAddingItems(View view) + { + if (!Application.UseVisualStyles) + { + return; + } + + using ListView listView = GetListViewItemWithInvisibleItems(view); + AccessibleObject accessibleObject = listView.Groups[0].AccessibilityObject; + + Assert.Equal(2, accessibleObject.GetChildCount()); + + listView.Items.Add(listView.Groups[0].Items[0]); + listView.Items.Add(listView.Groups[0].Items[3]); + + Assert.Equal(4, accessibleObject.GetChildCount()); + Assert.True(listView.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(View.Details)] + [InlineData(View.LargeIcon)] + [InlineData(View.SmallIcon)] + [InlineData(View.Tile)] + public void ListViewAccessibleObject_GetChildCount_ReturnsExpected_InvisibleItems_AfterRemovingItems(View view) + { + if (!Application.UseVisualStyles) + { + return; + } + + using ListView listView = GetListViewItemWithInvisibleItems(view); + AccessibleObject accessibleObject = listView.Groups[0].AccessibilityObject; + + Assert.Equal(2, accessibleObject.GetChildCount()); + + listView.Items.RemoveAt(1); + + Assert.Equal(1, accessibleObject.GetChildCount()); + + listView.Items.RemoveAt(0); + + Assert.Equal(0, accessibleObject.GetChildCount()); + Assert.True(listView.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(View.Details)] + [InlineData(View.LargeIcon)] + [InlineData(View.SmallIcon)] + [InlineData(View.Tile)] + public void ListViewAccessibleObject_GetChild_ReturnsExpected_InvisibleItems(View view) + { + if (!Application.UseVisualStyles) + { + return; + } + + using ListView listView = GetListViewItemWithInvisibleItems(view); + AccessibleObject accessibleObject = listView.Groups[0].AccessibilityObject; + + Assert.Equal(listView.Groups[0].Items[1].AccessibilityObject, accessibleObject.GetChild(0)); + Assert.Equal(listView.Groups[0].Items[2].AccessibilityObject, accessibleObject.GetChild(1)); + Assert.Null(accessibleObject.GetChild(2)); + Assert.True(listView.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(View.Details)] + [InlineData(View.LargeIcon)] + [InlineData(View.SmallIcon)] + [InlineData(View.Tile)] + public void ListViewAccessibleObject_GetChild_ReturnsExpected_InvisibleItems_AfterAddingItems(View view) + { + if (!Application.UseVisualStyles) + { + return; + } + + using ListView listView = GetListViewItemWithInvisibleItems(view); + AccessibleObject accessibleObject = listView.Groups[0].AccessibilityObject; + + listView.Items.Add(listView.Groups[0].Items[0]); + listView.Items.Add(listView.Groups[0].Items[3]); + + Assert.Equal(listView.Groups[0].Items[0].AccessibilityObject, accessibleObject.GetChild(0)); + Assert.Equal(listView.Groups[0].Items[1].AccessibilityObject, accessibleObject.GetChild(1)); + Assert.Equal(listView.Groups[0].Items[2].AccessibilityObject, accessibleObject.GetChild(2)); + Assert.Equal(listView.Groups[0].Items[3].AccessibilityObject, accessibleObject.GetChild(3)); + Assert.Null(accessibleObject.GetChild(4)); + Assert.True(listView.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(View.Details)] + [InlineData(View.LargeIcon)] + [InlineData(View.SmallIcon)] + [InlineData(View.Tile)] + public void ListViewAccessibleObject_GetChild_ReturnsExpected_InvisibleItems_AfterRemovingItems(View view) + { + if (!Application.UseVisualStyles) + { + return; + } + + using ListView listView = GetListViewItemWithInvisibleItems(view); + AccessibleObject accessibleObject = listView.Groups[0].AccessibilityObject; + + listView.Items.RemoveAt(1); + + Assert.Equal(listView.Groups[0].Items[1].AccessibilityObject, accessibleObject.GetChild(0)); + Assert.Null(accessibleObject.GetChild(1)); + Assert.True(listView.IsHandleCreated); + } + + private ListView GetListViewItemWithEmptyGroups(View view) + { + ListView listView = new ListView() { View = view }; + listView.CreateControl(); + ListViewGroup listViewGroupWithoutItems = new("Group without items"); + ListViewGroup listViewGroupWithItems1 = new("Group with item 1"); + ListViewGroup listViewGroupWithItems2 = new("Group with item 2"); + ListViewGroup listViewGroupWithInvisibleItems = new("Group with invisible item"); + listView.Groups.Add(listViewGroupWithoutItems); + listView.Groups.Add(listViewGroupWithItems1); + listView.Groups.Add(listViewGroupWithItems2); + listView.Groups.Add(listViewGroupWithInvisibleItems); + ListViewItem listViewItem1 = new(); + ListViewItem listViewItem2 = new(); + ListViewItem listViewItem3 = new(); + listView.Items.Add(listViewItem1); + listView.Items.Add(listViewItem2); + listViewGroupWithItems1.Items.Add(listViewItem1); + listViewGroupWithItems2.Items.Add(listViewItem2); + listViewGroupWithInvisibleItems.Items.Add(listViewItem3); + + return listView; + } + + private ListView GetListViewItemWithInvisibleItems(View view) + { + ListView listView = new ListView() { View = view }; + listView.CreateControl(); + ListViewGroup listViewGroup = new("Test group"); + ListViewItem listViewInvisibleItem1 = new ListViewItem("Invisible item 1"); + ListViewItem listViewVisibleItem1 = new ListViewItem("Visible item 1"); + ListViewItem listViewInvisibleItem2 = new ListViewItem("Invisible item 1"); + ListViewItem listViewVisibleItem2 = new ListViewItem("Visible item 1"); + + listView.Groups.Add(listViewGroup); + listView.Items.AddRange(new ListViewItem[] { listViewVisibleItem1, listViewVisibleItem2 }); + listViewGroup.Items.AddRange(new ListViewItem[] + { + listViewInvisibleItem1, listViewVisibleItem1, + listViewVisibleItem2, listViewInvisibleItem2 + }); + + return listView; + } } } 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 index c7d2fa5fbc7..b13f0b31814 100644 --- 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 @@ -1482,5 +1482,112 @@ public void ListViewItemAccessibleObject_Toggle_Invoke(View view, bool showGroup Assert.False(listViewItem.Checked); Assert.Equal(createHandle, listView.IsHandleCreated); } + + [WinFormsTheory] + [InlineData(View.Details)] + [InlineData(View.LargeIcon)] + [InlineData(View.SmallIcon)] + [InlineData(View.Tile)] + public void ListViewAccessibleObject_FragmentNaviage_Sibling_ReturnsExpected_InvisibleItems(View view) + { + if (!Application.UseVisualStyles) + { + return; + } + + using ListView listView = GetListViewItemWithInvisibleItems(view); + + Assert.Null(GetAccessibleObject(1).FragmentNavigate(UiaCore.NavigateDirection.PreviousSibling)); + Assert.Equal(GetAccessibleObject(2), GetAccessibleObject(1).FragmentNavigate(UiaCore.NavigateDirection.NextSibling)); + Assert.Equal(GetAccessibleObject(1), GetAccessibleObject(2).FragmentNavigate(UiaCore.NavigateDirection.PreviousSibling)); + Assert.Null(GetAccessibleObject(2).FragmentNavigate(UiaCore.NavigateDirection.NextSibling)); + Assert.True(listView.IsHandleCreated); + + AccessibleObject GetAccessibleObject(int index) => listView.Groups[0].Items[index].AccessibilityObject; + } + + [WinFormsTheory] + [InlineData(View.Details)] + [InlineData(View.LargeIcon)] + [InlineData(View.SmallIcon)] + [InlineData(View.Tile)] + public void ListViewAccessibleObject_FragmentNaviage_Sibling_ReturnsExpected_InvisibleItems_AfterAddingItems(View view) + { + if (!Application.UseVisualStyles) + { + return; + } + + using ListView listView = GetListViewItemWithInvisibleItems(view); + + Assert.Null(GetAccessibleObject(1).FragmentNavigate(UiaCore.NavigateDirection.PreviousSibling)); + Assert.Equal(GetAccessibleObject(2), GetAccessibleObject(1).FragmentNavigate(UiaCore.NavigateDirection.NextSibling)); + Assert.Equal(GetAccessibleObject(1), GetAccessibleObject(2).FragmentNavigate(UiaCore.NavigateDirection.PreviousSibling)); + Assert.Null(GetAccessibleObject(2).FragmentNavigate(UiaCore.NavigateDirection.NextSibling)); + + listView.Items.Add(listView.Groups[0].Items[0]); + listView.Items.Add(listView.Groups[0].Items[3]); + + Assert.Null(GetAccessibleObject(0).FragmentNavigate(UiaCore.NavigateDirection.PreviousSibling)); + Assert.Equal(GetAccessibleObject(1), GetAccessibleObject(0).FragmentNavigate(UiaCore.NavigateDirection.NextSibling)); + Assert.Equal(GetAccessibleObject(0), GetAccessibleObject(1).FragmentNavigate(UiaCore.NavigateDirection.PreviousSibling)); + Assert.Equal(GetAccessibleObject(2), GetAccessibleObject(1).FragmentNavigate(UiaCore.NavigateDirection.NextSibling)); + Assert.Equal(GetAccessibleObject(1), GetAccessibleObject(2).FragmentNavigate(UiaCore.NavigateDirection.PreviousSibling)); + Assert.Equal(GetAccessibleObject(3), GetAccessibleObject(2).FragmentNavigate(UiaCore.NavigateDirection.NextSibling)); + Assert.Equal(GetAccessibleObject(2), GetAccessibleObject(3).FragmentNavigate(UiaCore.NavigateDirection.PreviousSibling)); + Assert.Null(GetAccessibleObject(3).FragmentNavigate(UiaCore.NavigateDirection.NextSibling)); + Assert.True(listView.IsHandleCreated); + + AccessibleObject GetAccessibleObject(int index) => listView.Groups[0].Items[index].AccessibilityObject; + } + + [WinFormsTheory] + [InlineData(View.Details)] + [InlineData(View.LargeIcon)] + [InlineData(View.SmallIcon)] + [InlineData(View.Tile)] + public void ListViewAccessibleObject_FragmentNaviage_Sibling_ReturnsExpected_InvisibleItems_AfterRemovingItems(View view) + { + if (!Application.UseVisualStyles) + { + return; + } + + using ListView listView = GetListViewItemWithInvisibleItems(view); + + Assert.Null(GetAccessibleObject(1).FragmentNavigate(UiaCore.NavigateDirection.PreviousSibling)); + Assert.Equal(GetAccessibleObject(2), GetAccessibleObject(1).FragmentNavigate(UiaCore.NavigateDirection.NextSibling)); + Assert.Equal(GetAccessibleObject(1), GetAccessibleObject(2).FragmentNavigate(UiaCore.NavigateDirection.PreviousSibling)); + Assert.Null(GetAccessibleObject(2).FragmentNavigate(UiaCore.NavigateDirection.NextSibling)); + + listView.Items.RemoveAt(1); + + Assert.Null(GetAccessibleObject(0).FragmentNavigate(UiaCore.NavigateDirection.PreviousSibling)); + Assert.Null(GetAccessibleObject(0).FragmentNavigate(UiaCore.NavigateDirection.NextSibling)); + Assert.True(listView.IsHandleCreated); + + AccessibleObject GetAccessibleObject(int index) => listView.Groups[0].Items[index].AccessibilityObject; + } + + private ListView GetListViewItemWithInvisibleItems(View view) + { + ListView listView = new ListView() { View = view }; + listView.CreateControl(); + ListViewGroup listViewGroup = new("Test group"); + ListViewItem listViewInvisibleItem1 = new ListViewItem("Invisible item 1"); + ListViewItem listViewVisibleItem1 = new ListViewItem("Visible item 1"); + ListViewItem listViewInvisibleItem2 = new ListViewItem("Invisible item 1"); + ListViewItem listViewVisibleItem2 = new ListViewItem("Visible item 1"); + + listView.Groups.Add(listViewGroup); + listView.Items.AddRange(new ListViewItem[] { listViewVisibleItem1, listViewVisibleItem2 }); + listViewGroup.Items.AddRange(new ListViewItem[] + { + listViewInvisibleItem1, listViewVisibleItem1, + listViewVisibleItem2, listViewInvisibleItem2 + }); + + return listView; + } } }