File: System\Windows\Forms\Controls\ListView\ListViewItem.ListViewSubItem.ListViewSubItemAccessibleObject.cs
Web Access
Project: src\src\System.Windows.Forms\src\System.Windows.Forms.csproj (System.Windows.Forms)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Drawing;
using Windows.Win32.System.Variant;
using Windows.Win32.UI.Accessibility;
 
namespace System.Windows.Forms;
 
public partial class ListViewItem
{
    public partial class ListViewSubItem
    {
        internal sealed class ListViewSubItemAccessibleObject : AccessibleObject
        {
            private readonly ListView _owningListView;
            private readonly ListViewItem _owningItem;
 
            // This is necessary for the "Details" view,  when there is no ListViewSubItem,
            // but the cell for it is displayed and the user can interact with it.
            internal ListViewSubItem? OwningSubItem { get; private set; }
 
            public ListViewSubItemAccessibleObject(ListViewSubItem? owningSubItem, ListViewItem owningItem)
            {
                OwningSubItem = owningSubItem;
                _owningItem = owningItem.OrThrowIfNull();
                _owningListView = owningItem.ListView ?? owningItem.Group?.ListView ?? throw new InvalidOperationException(nameof(owningItem.ListView));
            }
 
            internal override IRawElementProviderFragmentRoot.Interface FragmentRoot => _owningListView.AccessibilityObject;
 
            public override Rectangle Bounds
            {
                get
                {
                    int index = ParentInternal.GetChildIndex(this);
                    if (index == InvalidIndex)
                    {
                        return Rectangle.Empty;
                    }
 
                    Rectangle bounds = ParentInternal.GetSubItemBounds(index);
                    if (bounds.IsEmpty)
                    {
                        return bounds;
                    }
 
                    // Previously bounds was provided using MSAA,
                    // but using UIA we found out that PInvoke.SendMessage work incorrectly.
                    // When we need to get bounds for first sub item it will return width of all item.
                    int width = bounds.Width;
 
                    if (!_owningListView.FullRowSelect && index == ParentInternal.FirstSubItemIndex && _owningListView.Columns.Count > 1)
                    {
                        width = ParentInternal.GetSubItemBounds(ParentInternal.FirstSubItemIndex + 1).X - bounds.X;
                    }
 
                    if (width <= 0)
                    {
                        return Rectangle.Empty;
                    }
 
                    return new Rectangle(
                        _owningListView.AccessibilityObject.Bounds.X + bounds.X,
                        _owningListView.AccessibilityObject.Bounds.Y + bounds.Y,
                        width, bounds.Height);
                }
            }
 
            internal override IRawElementProviderFragment.Interface? FragmentNavigate(NavigateDirection direction)
                => direction switch
                {
                    NavigateDirection.NavigateDirection_Parent
                        => ParentInternal,
                    NavigateDirection.NavigateDirection_NextSibling
                        => ParentInternal.GetChildInternal(ParentInternal.GetChildIndex(this) + 1),
                    NavigateDirection.NavigateDirection_PreviousSibling
                        => ParentInternal.GetChildInternal(ParentInternal.GetChildIndex(this) - 1),
                    NavigateDirection.NavigateDirection_FirstChild => GetChild(),
                    NavigateDirection.NavigateDirection_LastChild => GetChild(),
                    _ => base.FragmentNavigate(direction)
                };
 
            internal override int Column
                => _owningListView.View == View.Details
                    ? ParentInternal.GetChildIndex(this) - ParentInternal.FirstSubItemIndex
                    : InvalidIndex;
 
            /// <summary>
            ///  Gets or sets the accessible name.
            /// </summary>
            public override string? Name => base.Name ?? OwningSubItem?.Text ?? string.Empty;
 
            internal override bool CanGetNameInternal => false;
 
            public override AccessibleObject Parent => ParentInternal;
 
            private protected override bool IsInternal => true;
 
            private ListViewItemBaseAccessibleObject ParentInternal
                => (ListViewItemBaseAccessibleObject)_owningItem.AccessibilityObject;
 
            internal override int[] RuntimeId
            {
                get
                {
                    int[] id = Parent.RuntimeId;
 
                    Debug.Assert(id.Length >= 4);
 
                    return [id[0], id[1], id[2], id[3], GetHashCode()];
                }
            }
 
            internal override VARIANT GetPropertyValue(UIA_PROPERTY_ID propertyID)
                => propertyID switch
                {
                    // All subitems are "text". Some of them can be editable, if ListView.LabelEdit is true.
                    // In this case, an edit field appears when editing. This field has own accessible object, that
                    // has the "edit" control type, and it supports the Text pattern. And its owning subitem accessible
                    // object has the "text" control type, because it is just a container for the edit field.
                    UIA_PROPERTY_ID.UIA_ControlTypePropertyId => (VARIANT)(int)UIA_CONTROLTYPE_ID.UIA_TextControlTypeId,
                    UIA_PROPERTY_ID.UIA_HasKeyboardFocusPropertyId => (VARIANT)(_owningListView.Focused && _owningListView.FocusedItem == _owningItem),
                    UIA_PROPERTY_ID.UIA_IsEnabledPropertyId => (VARIANT)_owningListView.Enabled,
                    UIA_PROPERTY_ID.UIA_IsKeyboardFocusablePropertyId => (VARIANT)State.HasFlag(AccessibleStates.Focusable),
                    UIA_PROPERTY_ID.UIA_ProcessIdPropertyId => (VARIANT)Environment.ProcessId,
                    _ => base.GetPropertyValue(propertyID)
                };
 
            /// <summary>
            ///  Gets the accessible state.
            /// </summary>
            public override AccessibleStates State
                => AccessibleStates.Focusable;
 
            internal override IRawElementProviderSimple.Interface ContainingGrid
                => _owningListView.AccessibilityObject;
 
            internal override int Row => _owningItem.Index;
 
            internal override IRawElementProviderSimple.Interface[]? GetColumnHeaderItems()
                => _owningListView.View == View.Details && Column > -1
                    ? [_owningListView.Columns[Column].AccessibilityObject]
                    : null;
 
            internal override bool IsPatternSupported(UIA_PATTERN_ID patternId)
            {
                if (patternId is UIA_PATTERN_ID.UIA_GridItemPatternId or UIA_PATTERN_ID.UIA_TableItemPatternId)
                {
                    return _owningListView.View == View.Details;
                }
 
                return base.IsPatternSupported(patternId);
            }
 
            private protected override string AutomationId
                => $"{nameof(ListViewSubItem)}-{ParentInternal.GetChildIndex(this)}";
 
            private AccessibleObject? GetChild()
            {
                if (_owningListView._labelEdit is { } labelEdit)
                {
                    return labelEdit.AccessibilityObject;
                }
 
                return null;
            }
        }
    }
}