| File: System\Windows\Forms\Controls\ComboBox\ComboBox.ComboBoxChildListUiaProvider.cs | Web Access |
| Project: src\winforms\src\System.Windows.Forms\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; using static System.Windows.Forms.ComboBox.ObjectCollection; namespace System.Windows.Forms; public partial class ComboBox { /// <summary> /// Represents the ComboBox's child (inner) list native window control accessible object /// with UI Automation provider functionality. /// </summary> internal sealed class ComboBoxChildListUiaProvider : ChildAccessibleObject { private const string COMBO_BOX_LIST_AUTOMATION_ID = "1000"; private readonly ComboBox _owningComboBox; private readonly HWND _childListControlhandle; public ComboBoxChildListUiaProvider(ComboBox owningComboBox, HWND childListControlhandle) : base(owningComboBox, childListControlhandle) { _owningComboBox = owningComboBox; _childListControlhandle = childListControlhandle; } private protected override string AutomationId => COMBO_BOX_LIST_AUTOMATION_ID; internal override Rectangle BoundingRectangle { get { PInvokeCore.GetWindowRect(_owningComboBox.GetListNativeWindow(), out var rect); return rect; } } internal override unsafe IRawElementProviderFragment.Interface? ElementProviderFromPoint(double x, double y) { using var accessible = SystemIAccessible.TryGetIAccessible(out HRESULT result); if (result.Succeeded) { result = accessible.Value->accHitTest((int)x, (int)y, out VARIANT child); if (result.Succeeded && child.vt == VARENUM.VT_I4) { return GetChildFragment((int)child - 1); } else { child.Dispose(); return null; } } return base.ElementProviderFromPoint(x, y); } internal override IRawElementProviderFragment.Interface? FragmentNavigate(NavigateDirection direction) { if (!_owningComboBox.IsHandleCreated || // Created is set to false in WM_DESTROY, but the window Handle is released on NCDESTROY, which comes after DESTROY. // But between these calls, AccessibleObject can be recreated and might cause memory leaks. !_owningComboBox.Created) { return null; } switch (direction) { case NavigateDirection.NavigateDirection_Parent: return _owningComboBox.AccessibilityObject; case NavigateDirection.NavigateDirection_FirstChild: return GetChildFragment(0); case NavigateDirection.NavigateDirection_LastChild: int childFragmentCount = GetChildFragmentCount(); if (childFragmentCount > 0) { return GetChildFragment(childFragmentCount - 1); } return null; case NavigateDirection.NavigateDirection_NextSibling: return _owningComboBox.DropDownStyle == ComboBoxStyle.DropDownList ? _owningComboBox.ChildTextAccessibleObject : _owningComboBox.ChildEditAccessibleObject; case NavigateDirection.NavigateDirection_PreviousSibling: // A workaround for an issue with an Inspect not responding. It also simulates native control behavior. return _owningComboBox.DropDownStyle == ComboBoxStyle.Simple ? _owningComboBox.ChildListAccessibleObject : null; default: return base.FragmentNavigate(direction); } } internal override IRawElementProviderFragmentRoot.Interface FragmentRoot => _owningComboBox.AccessibilityObject; public AccessibleObject? GetChildFragment(int index) { if (index < 0 || index >= _owningComboBox.Items.Count) { return null; } if (_owningComboBox.AccessibilityObject is not ComboBoxAccessibleObject comboBoxAccessibleObject) { return null; } Entry item = _owningComboBox.Entries[index]; return item is null ? null : comboBoxAccessibleObject.ItemAccessibleObjects.GetComboBoxItemAccessibleObject(item); } public int GetChildFragmentCount() { return _owningComboBox.Items.Count; } internal override VARIANT GetPropertyValue(UIA_PROPERTY_ID propertyID) => propertyID switch { UIA_PROPERTY_ID.UIA_ControlTypePropertyId => (VARIANT)(int)UIA_CONTROLTYPE_ID.UIA_ListControlTypeId, UIA_PROPERTY_ID.UIA_HasKeyboardFocusPropertyId => // Narrator should keep the keyboard focus on th ComboBox itself but not on the DropDown. VARIANT.False, UIA_PROPERTY_ID.UIA_IsEnabledPropertyId => (VARIANT)_owningComboBox.Enabled, UIA_PROPERTY_ID.UIA_IsKeyboardFocusablePropertyId => (VARIANT)State.HasFlag(AccessibleStates.Focusable), UIA_PROPERTY_ID.UIA_IsOffscreenPropertyId => VARIANT.False, UIA_PROPERTY_ID.UIA_IsSelectionPatternAvailablePropertyId => VARIANT.True, UIA_PROPERTY_ID.UIA_NativeWindowHandlePropertyId => UIAHelper.WindowHandleToVariant(_childListControlhandle), _ => base.GetPropertyValue(propertyID) }; internal override IRawElementProviderFragment.Interface? GetFocus() => GetFocused(); public override AccessibleObject? GetFocused() { if (!_owningComboBox.IsHandleCreated) { return null; } int selectedIndex = _owningComboBox.SelectedIndex; return GetChildFragment(selectedIndex); } private protected override bool IsInternal => true; internal override IRawElementProviderSimple.Interface[] GetSelection() { if (!_owningComboBox.IsHandleCreated) { return []; } int selectedIndex = _owningComboBox.SelectedIndex; AccessibleObject? itemAccessibleObject = GetChildFragment(selectedIndex); return itemAccessibleObject is not null ? [itemAccessibleObject] : []; } internal override bool CanSelectMultiple => false; internal override bool IsSelectionRequired => true; internal override bool IsPatternSupported(UIA_PATTERN_ID patternId) => patternId switch { UIA_PATTERN_ID.UIA_LegacyIAccessiblePatternId or UIA_PATTERN_ID.UIA_SelectionPatternId => true, _ => base.IsPatternSupported(patternId), }; internal override unsafe IRawElementProviderSimple* HostRawElementProvider { get { PInvoke.UiaHostProviderFromHwnd(new HandleRef<HWND>(this, _childListControlhandle), out IRawElementProviderSimple* provider); return provider; } } internal override int[] RuntimeId => [ RuntimeIDFirstItem, (int)_owningComboBox.InternalHandle, _owningComboBox.GetListNativeWindowRuntimeIdPart() ]; public override AccessibleStates State { get { AccessibleStates state = AccessibleStates.Focusable; if (_owningComboBox.Focused) { state |= AccessibleStates.Focused; } return state; } } } }