File: System\Windows\Forms\Controls\ComboBox\ComboBox.ComboBoxItemAccessibleObject.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.Com;
using Windows.Win32.System.Variant;
using Windows.Win32.UI.Accessibility;
using static System.Windows.Forms.ComboBox.ObjectCollection;
 
namespace System.Windows.Forms;
 
public partial class ComboBox
{
    /// <summary>
    ///  Represents the <see cref="ComboBox"/> item accessible object.
    /// </summary>
    internal sealed class ComboBoxItemAccessibleObject : AccessibleObject
    {
        private readonly ComboBox _owningComboBox;
        private readonly Entry _owningItem;
 
        /// <summary>
        ///  Initializes new instance of <see cref="ComboBox"/> item accessible object.
        /// </summary>
        /// <param name="owningComboBox">The owning <see cref="ComboBox"/>.</param>
        /// <param name="owningItem">The owning <see cref="ComboBox"/> item.</param>
        public ComboBoxItemAccessibleObject(ComboBox owningComboBox, Entry owningItem)
        {
            _owningComboBox = owningComboBox.OrThrowIfNull();
            _owningItem = owningItem;
        }
 
        public override Rectangle Bounds
        {
            get
            {
                int currentIndex = GetCurrentIndex();
                var listHandle = _owningComboBox.GetListHandle();
                RECT itemRect = default;
 
                int result = (int)PInvokeCore.SendMessage(
                    listHandle,
                    PInvoke.LB_GETITEMRECT,
                    (WPARAM)currentIndex,
                    ref itemRect);
 
                if (result == PInvoke.LB_ERR)
                {
                    return Rectangle.Empty;
                }
 
                // Translate the item rect to screen coordinates
                RECT translated = itemRect;
                PInvokeCore.MapWindowPoints(listHandle, HWND.Null, ref translated);
                return translated;
            }
        }
 
        public override string? DefaultAction => GetDefaultActionInternal().ToNullableStringAndFree();
 
        internal override BSTR GetDefaultActionInternal() =>
            _owningComboBox.ChildListAccessibleObject.SystemIAccessible.TryGetDefaultAction(GetChildId());
 
        private protected override bool IsInternal => true;
 
        internal override IRawElementProviderFragment.Interface? FragmentNavigate(NavigateDirection direction)
        {
            if (direction == NavigateDirection.NavigateDirection_Parent)
            {
                return _owningComboBox.ChildListAccessibleObject;
            }
 
            if (_owningComboBox.ChildListAccessibleObject is not ComboBoxChildListUiaProvider comboBoxChildListUiaProvider)
            {
                return base.FragmentNavigate(direction);
            }
 
            switch (direction)
            {
                case NavigateDirection.NavigateDirection_NextSibling:
                    int currentIndex = GetCurrentIndex();
                    int itemsCount = comboBoxChildListUiaProvider.GetChildFragmentCount();
                    int nextItemIndex = currentIndex + 1;
                    if (itemsCount > nextItemIndex)
                    {
                        return comboBoxChildListUiaProvider.GetChildFragment(nextItemIndex);
                    }
 
                    break;
                case NavigateDirection.NavigateDirection_PreviousSibling:
                    currentIndex = GetCurrentIndex();
                    int previousItemIndex = currentIndex - 1;
                    if (previousItemIndex >= 0)
                    {
                        return comboBoxChildListUiaProvider.GetChildFragment(previousItemIndex);
                    }
 
                    break;
            }
 
            return base.FragmentNavigate(direction);
        }
 
        internal override IRawElementProviderFragmentRoot.Interface FragmentRoot => _owningComboBox.AccessibilityObject;
 
        private int GetCurrentIndex() => _owningComboBox.Items.InnerList.IndexOf(_owningItem);
 
        // Index is zero-based, Child ID is 1-based.
        internal override int GetChildId() => GetCurrentIndex() + 1;
 
        internal override unsafe VARIANT GetPropertyValue(UIA_PROPERTY_ID propertyID) =>
            propertyID switch
            {
                UIA_PROPERTY_ID.UIA_ControlTypePropertyId => (VARIANT)(int)UIA_CONTROLTYPE_ID.UIA_ListItemControlTypeId,
                UIA_PROPERTY_ID.UIA_HasKeyboardFocusPropertyId => (VARIANT)(_owningComboBox.Focused && _owningComboBox.SelectedIndex == GetCurrentIndex()),
                UIA_PROPERTY_ID.UIA_IsContentElementPropertyId => VARIANT.True,
                UIA_PROPERTY_ID.UIA_IsControlElementPropertyId => VARIANT.True,
                UIA_PROPERTY_ID.UIA_IsEnabledPropertyId => (VARIANT)_owningComboBox.Enabled,
                UIA_PROPERTY_ID.UIA_IsKeyboardFocusablePropertyId => (VARIANT)State.HasFlag(AccessibleStates.Focusable),
                UIA_PROPERTY_ID.UIA_SelectionItemIsSelectedPropertyId => (VARIANT)State.HasFlag(AccessibleStates.Selected),
                UIA_PROPERTY_ID.UIA_SelectionItemSelectionContainerPropertyId => (VARIANT)ComHelpers.GetComPointer<IUnknown>(_owningComboBox.ChildListAccessibleObject),
                _ => base.GetPropertyValue(propertyID)
            };
 
        public override string? Help => GetHelpInternal().ToNullableStringAndFree();
 
        internal override BSTR GetHelpInternal() => _owningComboBox.ChildListAccessibleObject.SystemIAccessible.TryGetHelp(GetChildId());
 
        internal override bool IsPatternSupported(UIA_PATTERN_ID patternId)
        {
            return patternId switch
            {
                UIA_PATTERN_ID.UIA_LegacyIAccessiblePatternId
                    or UIA_PATTERN_ID.UIA_InvokePatternId
                    or UIA_PATTERN_ID.UIA_ScrollItemPatternId
                    or UIA_PATTERN_ID.UIA_SelectionItemPatternId => true,
                _ => base.IsPatternSupported(patternId),
            };
        }
 
        public override string? Name => _owningComboBox is null ? base.Name : _owningComboBox.GetItemText(_owningItem.Item);
 
        internal override bool CanGetNameInternal => _owningComboBox is null;
 
        public override AccessibleRole Role
            => _owningComboBox.ChildListAccessibleObject.SystemIAccessible.TryGetRole(GetChildId());
 
        internal override int[] RuntimeId =>
        [
            RuntimeIDFirstItem,
            (int)_owningComboBox.InternalHandle,
            _owningComboBox.GetListNativeWindowRuntimeIdPart(),
            _owningItem.GetHashCode()
        ];
 
        public override AccessibleStates State
        {
            get
            {
                AccessibleStates state = _owningComboBox.ChildListAccessibleObject.SystemIAccessible.TryGetState(GetChildId());
 
                if (!_owningComboBox.DroppedDown || !_owningComboBox.ChildListAccessibleObject.Bounds.IntersectsWith(Bounds))
                {
                    state |= AccessibleStates.Offscreen;
                }
 
                return state;
            }
        }
 
        internal override void ScrollIntoView()
        {
            if (!_owningComboBox.IsHandleCreated || !_owningComboBox.Enabled)
            {
                return;
            }
 
            Rectangle listBounds = _owningComboBox.ChildListAccessibleObject.Bounds;
            if (listBounds.IntersectsWith(Bounds))
            {
                // Do nothing because the item is already visible
                return;
            }
 
            PInvokeCore.SendMessage(_owningComboBox, PInvoke.CB_SETTOPINDEX, (WPARAM)GetCurrentIndex());
        }
 
        internal override void SetFocus()
        {
            RaiseAutomationEvent(UIA_EVENT_ID.UIA_AutomationFocusChangedEventId);
 
            base.SetFocus();
        }
 
        internal override unsafe void SelectItem()
        {
            if (!_owningComboBox.IsHandleCreated)
            {
                return;
            }
 
            _owningComboBox.SelectedIndex = GetCurrentIndex();
            PInvoke.InvalidateRect(_owningComboBox.GetListHandle(), lpRect: null, bErase: false);
        }
 
        internal override void AddToSelection()
        {
            if (!_owningComboBox.IsHandleCreated)
            {
                return;
            }
 
            SelectItem();
        }
 
        internal override void RemoveFromSelection()
        {
            // Do nothing, C++ implementation returns UIA_E_INVALIDOPERATION 0x80131509
        }
 
        internal override bool IsItemSelected => (State & AccessibleStates.Selected) != 0;
 
        internal override IRawElementProviderSimple.Interface ItemSelectionContainer => _owningComboBox.ChildListAccessibleObject;
    }
}