File: System\Windows\Forms\Controls\ListView\ListViewItem.ListViewItemDetailsAccessibleObject.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.UI.Accessibility;
 
namespace System.Windows.Forms;
 
public partial class ListViewItem
{
    internal sealed class ListViewItemDetailsAccessibleObject : ListViewItemBaseAccessibleObject
    {
        private const int ImageAccessibleObjectIndex = 0;
        private readonly Dictionary<int, AccessibleObject> _listViewSubItemAccessibleObjects;
 
        public ListViewItemDetailsAccessibleObject(ListViewItem owningItem) : base(owningItem)
        {
            _listViewSubItemAccessibleObjects = [];
        }
 
        internal override int FirstSubItemIndex => HasImage ? 1 : 0;
 
        private int LastChildIndex => HasImage ? _owningListView.Columns.Count : _owningListView.Columns.Count - 1;
 
        protected override View View => View.Details;
 
        /// <summary>
        ///  Converts the provided index of the <see cref="AccessibleObject"/>'s child to an index of a <see cref="ListViewSubItem"/>.
        /// </summary>
        /// <param name="accessibleChildIndex">The index of the child <see cref="AccessibleObject"/>.</param>
        /// <returns>The index of an owning <see cref="ListViewSubItem"/>'s object.</returns>
        private int AccessibleChildToSubItemIndex(int accessibleChildIndex) => HasImage
            ? _owningListView.Columns[accessibleChildIndex - 1]._correspondingListViewSubItemIndex
            : _owningListView.Columns[accessibleChildIndex]._correspondingListViewSubItemIndex;
 
        internal override IRawElementProviderFragment.Interface? FragmentNavigate(NavigateDirection direction)
            => direction switch
            {
                NavigateDirection.NavigateDirection_FirstChild => GetChild(0),
                NavigateDirection.NavigateDirection_LastChild => GetChild(LastChildIndex),
                _ => base.FragmentNavigate(direction),
            };
 
        public override AccessibleObject? GetChild(int accessibleChildIndex)
        {
            if (_owningListView.View != View.Details)
            {
                throw new InvalidOperationException(string.Format(SR.ListViewItemAccessibilityObjectInvalidViewException, nameof(View.Details)));
            }
 
            // If the ListView does not support ListViewSubItems, the accessibleChildIndex is greater than the number of columns
            // or the accessibleChildIndex is negative, then we return null
            return !_owningListView.SupportsListViewSubItems || accessibleChildIndex > LastChildIndex || accessibleChildIndex < 0
                ? null
                : GetDetailsSubItemOrFakeInternal(accessibleChildIndex);
        }
 
        internal AccessibleObject? GetChild(int subItemIndex, Point point)
        {
            if (!HasImage || subItemIndex > 0)
            {
                return GetDetailsSubItemOrFake(subItemIndex);
            }
 
            return GetDetailsSubItemOrFakeInternal(ImageAccessibleObjectIndex) is ListViewItemImageAccessibleObject imageAccessibleObject &&
                   imageAccessibleObject.GetImageRectangle().Contains(point)
                ? imageAccessibleObject
                : GetDetailsSubItemOrFake(0);
        }
 
        public override int GetChildCount()
        {
            if (_owningListView.View != View.Details)
            {
                throw new InvalidOperationException(string.Format(SR.ListViewItemAccessibilityObjectInvalidViewException, nameof(View.Details)));
            }
 
            return !_owningListView.IsHandleCreated || !_owningListView.SupportsListViewSubItems
                ? -1
                : LastChildIndex + 1;
        }
 
        internal override int GetChildIndex(AccessibleObject? child)
        {
            if (child is null || !_owningListView.SupportsListViewSubItems)
            {
                return InvalidIndex;
            }
 
            if (child is ListViewItemImageAccessibleObject)
            {
                return ImageAccessibleObjectIndex;
            }
 
            if (child is not ListViewSubItem.ListViewSubItemAccessibleObject subItemAccessibleObject)
            {
                return InvalidIndex;
            }
 
            if (subItemAccessibleObject.OwningSubItem is null)
            {
                return GetFakeSubItemIndex(subItemAccessibleObject);
            }
 
            int subItemIndex = _owningItem.SubItems.IndexOf(subItemAccessibleObject.OwningSubItem);
            int accessibleChildIndex = InvalidIndex;
            for (int i = 0; i < _owningListView.Columns.Count; i++)
            {
                if (_owningListView.Columns[i]._correspondingListViewSubItemIndex == subItemIndex)
                {
                    accessibleChildIndex = i + (HasImage ? 1 : 0);
                    break;
                }
            }
 
            return accessibleChildIndex > LastChildIndex ? InvalidIndex : accessibleChildIndex;
        }
 
        // This method returns an accessibility object for an existing ListViewSubItem, or creates a fake one
        // if the ListViewSubItem does not exist. 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 AccessibleObject? GetDetailsSubItemOrFake(int subItemIndex)
        {
            int accessibleChildIndex = HasImage ? subItemIndex + 1 : subItemIndex;
            return GetDetailsSubItemOrFakeInternal(accessibleChildIndex);
        }
 
        private AccessibleObject? GetDetailsSubItemOrFakeInternal(int accessibleChildIndex)
        {
            if (accessibleChildIndex == ImageAccessibleObjectIndex && HasImage)
            {
                if (_listViewSubItemAccessibleObjects.TryGetValue(accessibleChildIndex, out AccessibleObject? childAO))
                {
                    return childAO;
                }
 
                ListViewItemImageAccessibleObject imageAccessibleObject = new(_owningItem);
                _listViewSubItemAccessibleObjects.Add(accessibleChildIndex, imageAccessibleObject);
                return imageAccessibleObject;
            }
 
            int subItemIndex = AccessibleChildToSubItemIndex(accessibleChildIndex);
            if (subItemIndex < _owningItem.SubItems.Count)
            {
                _listViewSubItemAccessibleObjects.Remove(accessibleChildIndex);
 
                return _owningItem.SubItems[subItemIndex].AccessibilityObject;
            }
 
            if (_listViewSubItemAccessibleObjects.TryGetValue(accessibleChildIndex, out AccessibleObject? childAO2))
            {
                return childAO2;
            }
 
            ListViewSubItem.ListViewSubItemAccessibleObject fakeAccessibleObject = new(owningSubItem: null, _owningItem);
            _listViewSubItemAccessibleObjects.Add(accessibleChildIndex, fakeAccessibleObject);
            return fakeAccessibleObject;
        }
 
        // This method is required to get the accessibleChildIndex of the fake accessibility object.
        // Since the fake accessibility object
        // has no ListViewSubItem from which we could get an accessibleChildIndex,
        // we have to get its accessibleChildIndex from the dictionary
        private int GetFakeSubItemIndex(ListViewSubItem.ListViewSubItemAccessibleObject fakeAccessibleObject)
        {
            foreach (KeyValuePair<int, AccessibleObject> keyValuePair in _listViewSubItemAccessibleObjects)
            {
                if (keyValuePair.Value == fakeAccessibleObject)
                {
                    return keyValuePair.Key;
                }
            }
 
            return -1;
        }
 
        internal override Rectangle GetSubItemBounds(int accessibleChildIndex)
            => _owningListView.IsHandleCreated
                ? _owningListView.GetSubItemRect(_owningItem.Index, HasImage ? accessibleChildIndex - 1 : accessibleChildIndex)
                : Rectangle.Empty;
 
        /// <devdoc>
        /// .Caller should ensure that the current OS is Windows 8 or greater.
        /// </devdoc>
        internal override void ReleaseChildUiaProviders()
        {
            base.ReleaseChildUiaProviders();
 
            foreach (AccessibleObject accessibleObject in _listViewSubItemAccessibleObjects.Values)
            {
                PInvoke.UiaDisconnectProvider(accessibleObject, skipOSCheck: true);
            }
 
            _listViewSubItemAccessibleObjects.Clear();
        }
    }
}