Skip to content

Commit

Permalink
Fix rectangle of the ListViewGroup (#4800)
Browse files Browse the repository at this point in the history
Fixes #4778


## Proposed changes
- The issue with the incorrect rectangle is reproduced because the "FragmentRoot" property was not overridden. Added an override for the "FragmentRoot" property.
- Fixed issue with getting a rectangle for a ListViewGroup. Now, instead of the ListViewGroup index, we use the group ID. Fixed a issue with getting an incorrect ListViewGroup index.
- Added unit tests. Fixed typos in unit-tests naming


<!-- We are in TELL-MODE the following section must be completed -->

## Customer Impact
### Case 1
**Before fix:**
![Issue-4778-case1-before](https://user-images.githubusercontent.com/23376742/114997193-41c42500-9ea8-11eb-9d8d-dd2fed7be588.png)

**After fix:**
![Issue-4778-case1-after](https://user-images.githubusercontent.com/23376742/114997486-8e0f6500-9ea8-11eb-8e14-0aae753335ac.png)

### Case 2
**Before fix:**
![Issue-4778-case2-before](https://user-images.githubusercontent.com/23376742/114997915-0bd37080-9ea9-11eb-8203-d76619915de9.png)

**After fix:**
![Issue-4778-case2-after](https://user-images.githubusercontent.com/23376742/114997927-1130bb00-9ea9-11eb-80fe-bee59783f3cf.png)

## Regression? 

- Yes (from #3224)

## Risk

- Minimal

## Test methodology <!-- How did you ensure quality? -->
- CTI team
- unit tests

## Accessibility testing  <!-- Remove this section if PR does not change UI -->
- Inspector

## Test environment(s) <!-- Remove any that don't apply -->
- Microsoft Windows [Version 10.0.19041.388]
- .NET Core SDK: 6.0.100-preview.2.21155.3
  • Loading branch information
SergeySmirnov-Akvelon authored Apr 21, 2021
1 parent 7ecc0cf commit 76e7021
Show file tree
Hide file tree
Showing 8 changed files with 276 additions and 85 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// 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.

internal static partial class Interop
{
internal static partial class ComCtl32
{
public enum LVGGR
{
GROUP = 0,
HEADER = 1,
LABEL = 2,
SUBSETLINK = 3
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ internal override bool CanSelectMultiple
internal override int ColumnCount
=> _owningListView.Columns.Count;

private bool OwnerHasDefaultGroup
internal bool OwnerHasDefaultGroup
{
get
{
if (!_owningListView.IsHandleCreated || !_owningListView.ShowGroups || _owningListView.VirtualMode)
if (!_owningListView.ShowGroups || _owningListView.VirtualMode)
{
return false;
}
Expand Down Expand Up @@ -77,7 +77,7 @@ internal override int[]? RuntimeId
}

// ListViewGroup are not displayed when the ListView is in "List" view
private bool ShowGroupAccessibleObject => _owningListView.View != View.List && _owningListView.GroupsEnabled;
internal bool ShowGroupAccessibleObject => _owningListView.View != View.List && _owningListView.GroupsEnabled;

internal override UiaCore.IRawElementProviderFragment? ElementProviderFromPoint(double x, double y)
{
Expand Down
16 changes: 0 additions & 16 deletions src/System.Windows.Forms/src/System/Windows/Forms/ListView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3501,22 +3501,6 @@ 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);
}

/// <summary>
/// Returns the current ListViewItem corresponding to the specific
/// x,y co-ordinate.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using System.Collections.Generic;
using System.Drawing;
using System.Runtime.InteropServices;
using static System.Windows.Forms.ListView;
using static Interop;
using static Interop.ComCtl32;
Expand Down Expand Up @@ -42,28 +43,55 @@ public override Rectangle Bounds
{
get
{
if (!_owningListView.IsHandleCreated || _owningListView.VirtualMode)
if (!_owningListView.IsHandleCreated || !_owningListViewAccessibilityObject.ShowGroupAccessibleObject)
{
return Rectangle.Empty;
}

RECT groupRect = new RECT();
User32.SendMessageW(_owningListView, (User32.WM)ComCtl32.LVM.GETGROUPRECT, (IntPtr)CurrentIndex, ref groupRect);
if (GetVisibleItems().Count == 0)
{
return Rectangle.Empty;
}

int nativeGroupId = GetNativeGroupId();
if (nativeGroupId == -1)
{
return Rectangle.Empty;
}

return new Rectangle(
_owningListViewAccessibilityObject.Bounds.X + groupRect.left,
_owningListViewAccessibilityObject.Bounds.Y + groupRect.top,
groupRect.right - groupRect.left,
groupRect.bottom - groupRect.top);
LVGGR rectType = _owningGroup.CollapsedState == ListViewGroupCollapsedState.Collapsed
? LVGGR.HEADER
: LVGGR.GROUP;

// Get the native rectangle
RECT groupRect = new();

// Using the "top" property, we set which rectangle type of the group we want to get
// This is described in more detail in https://docs.microsoft.com/windows/win32/controls/lvm-getgrouprect
groupRect.top = (int)rectType;
User32.SendMessageW(_owningListView, (User32.WM)LVM.GETGROUPRECT, (IntPtr)nativeGroupId, ref groupRect);

// Using the following code, we limit the size of the ListViewGroup rectangle
// so that it does not go beyond the rectangle of the ListView
Rectangle listViewBounds = _owningListView.AccessibilityObject.Bounds;
groupRect = _owningListView.RectangleToScreen(groupRect);
groupRect.top = Math.Max(listViewBounds.Top, groupRect.top);
groupRect.bottom = Math.Min(listViewBounds.Bottom, groupRect.bottom);
groupRect.left = Math.Max(listViewBounds.Left, groupRect.left);
groupRect.right = Math.Min(listViewBounds.Right, groupRect.right);

return groupRect;
}
}

private int CurrentIndex
internal int CurrentIndex
// The default group has 0 index, as it is always displayed first.
=> _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);
? 0
// When calculating the index of other groups, we add a shift if the default group is displayed
: _owningListViewAccessibilityObject.OwnerHasDefaultGroup
? _owningListView.Groups.IndexOf(_owningGroup) + 1
: _owningListView.Groups.IndexOf(_owningGroup);

public override string DefaultAction
=> SR.AccessibleActionDoubleClick;
Expand All @@ -73,6 +101,8 @@ internal override UiaCore.ExpandCollapseState ExpandCollapseState
? UiaCore.ExpandCollapseState.Collapsed
: UiaCore.ExpandCollapseState.Expanded;

internal override UiaCore.IRawElementProviderFragmentRoot FragmentRoot => _owningListView.AccessibilityObject;

public override string Name
=> _owningGroup.Header;

Expand Down Expand Up @@ -131,7 +161,26 @@ private bool GetNativeFocus()
return false;
}

return LVGS.FOCUSED == unchecked((LVGS)(long)User32.SendMessageW(_owningListView, (User32.WM)LVM.GETGROUPSTATE, (IntPtr)CurrentIndex, (IntPtr)LVGS.FOCUSED));
int nativeGroupId = GetNativeGroupId();
if (nativeGroupId == -1)
{
return false;
}

return LVGS.FOCUSED == unchecked((LVGS)(long)User32.SendMessageW(_owningListView, (User32.WM)LVM.GETGROUPSTATE, (IntPtr)nativeGroupId, (IntPtr)LVGS.FOCUSED));
}

private unsafe int GetNativeGroupId()
{
LVGROUPW lvgroup = new LVGROUPW
{
cbSize = (uint)sizeof(LVGROUPW),
mask = LVGF.GROUPID,
};

return User32.SendMessageW(_owningListView, (User32.WM)LVM.GETGROUPINFOBYINDEX, (IntPtr)CurrentIndex, ref lvgroup) == IntPtr.Zero
? -1
: lvgroup.iGroupId;
}

internal override object? GetPropertyValue(UiaCore.UIA propertyID)
Expand All @@ -157,6 +206,19 @@ private bool GetNativeFocus()
internal IReadOnlyList<ListViewItem> GetVisibleItems()
{
List<ListViewItem> visibleItems = new();
if (_owningGroupIsDefault)
{
foreach (ListViewItem? listViewItem in _owningListView.Items)
{
if (listViewItem is not null && listViewItem.Group is null)
{
visibleItems.Add(listViewItem);
}
}

return visibleItems;
}

foreach (ListViewItem listViewItem in _owningGroup.Items)
{
if (listViewItem.ListView is not null)
Expand Down Expand Up @@ -213,26 +275,13 @@ internal IReadOnlyList<ListViewItem> GetVisibleItems()
return null;
}

if (!_owningGroupIsDefault)
IReadOnlyList<ListViewItem> visibleItems = GetVisibleItems();
if (index < 0 || index >= visibleItems.Count)
{
IReadOnlyList<ListViewItem> visibleItems = GetVisibleItems();
if (index < 0 || index >= visibleItems.Count)
{
return null;
}

return visibleItems[index].AccessibilityObject;
}

foreach (ListViewItem? item in _owningListView.Items)
{
if (item is not null && item.Group is null && index-- == 0)
{
return item.AccessibilityObject;
}
return null;
}

return null;
return visibleItems[index].AccessibilityObject;
}

private int GetChildIndex(AccessibleObject child)
Expand Down Expand Up @@ -300,23 +349,7 @@ public override int GetChildCount()
return -1;
}

if (_owningGroupIsDefault)
{
int count = 0;
foreach (ListViewItem? item in _owningListView.Items)
{
if (item is not null && item.Group is null)
{
count++;
}
}

return count;
}
else
{
return GetVisibleItems().Count;
}
return GetVisibleItems().Count;
}

internal override bool IsPatternSupported(UiaCore.UIA patternId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,10 @@ internal override int[]? RuntimeId
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[3] = _owningGroup.AccessibilityObject is ListViewGroupAccessibleObject listViewGroupAccessibleObject
? listViewGroupAccessibleObject.CurrentIndex
: -1;

runtimeId[4] = CurrentIndex;

return runtimeId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ public static IEnumerable<object[]> ListViewAccessibleObject_OwnerHasDefaultGrou
{
foreach (bool createHandle in new[] { true, false })
{
bool expected = showGroups && createHandle;
bool expected = showGroups;
yield return new object[] { showGroups, createHandle, expected };
}
}
Expand Down
Loading

0 comments on commit 76e7021

Please sign in to comment.