// 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(); } } } |