Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

{
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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

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