File: System\Windows\Forms\Controls\TabControl\TabPage.TabAccessibleObject.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 TabPage
{
    internal class TabAccessibleObject : AccessibleObject
    {
        private readonly TabPage _owningTabPage;
 
        public TabAccessibleObject(TabPage owningTabPage)
        {
            _owningTabPage = owningTabPage.OrThrowIfNull();
        }
 
        private protected override string AutomationId => _owningTabPage.Name;
 
        public override Rectangle Bounds
        {
            get
            {
                if (OwningTabControl is null || !OwningTabControl.IsHandleCreated || SystemIAccessibleInternal is null)
                {
                    return Rectangle.Empty;
                }
 
                int index = CurrentIndex;
 
                if (index == -1 || (State & AccessibleStates.Invisible) == AccessibleStates.Invisible)
                {
                    return Rectangle.Empty;
                }
 
                // The "GetChildId" method returns to the id of the TabControl element,
                // which allows to use the native "accLocation" method to get the "Bounds" property
                return SystemIAccessibleInternal.TryGetLocation(GetChildId());
            }
        }
 
        public override string? DefaultAction => GetDefaultActionInternal().ToNullableStringAndFree();
 
        private protected override bool IsInternal => true;
 
        internal override BSTR GetDefaultActionInternal() => SystemIAccessibleInternal.TryGetDefaultAction(GetChildId());
 
        public override string? Name => _owningTabPage.Text;
 
        internal override bool CanGetNameInternal => false;
 
        private TabControl? OwningTabControl => _owningTabPage.ParentInternal as TabControl;
 
        public override AccessibleRole Role => SystemIAccessibleInternal.TryGetRole(GetChildId());
 
        public override AccessibleStates State => SystemIAccessibleInternal.TryGetState(GetChildId());
 
        internal override IRawElementProviderFragmentRoot.Interface? FragmentRoot => OwningTabControl?.AccessibilityObject;
 
        internal override bool IsItemSelected => OwningTabControl?.SelectedTab == _owningTabPage;
 
        internal override IRawElementProviderSimple.Interface? ItemSelectionContainer => OwningTabControl?.AccessibilityObject;
 
        internal override int[] RuntimeId =>
        [
            RuntimeIDFirstItem,
            OwningTabControl is null
                ? (int)IntPtr.Zero
                : (int)OwningTabControl.InternalHandle,
            GetHashCode()
        ];
 
        private int CurrentIndex => OwningTabControl?.TabPages.IndexOf(_owningTabPage) ?? -1;
 
        private AgileComPointer<IAccessible>? SystemIAccessibleInternal
            => OwningTabControl?.AccessibilityObject.SystemIAccessible;
 
        public override void DoDefaultAction()
        {
            if (OwningTabControl is not null && OwningTabControl.IsHandleCreated && OwningTabControl.Enabled)
            {
                OwningTabControl.SelectedTab = _owningTabPage;
            }
        }
 
        internal override void AddToSelection() => DoDefaultAction();
 
        internal override IRawElementProviderFragment.Interface? FragmentNavigate(NavigateDirection direction)
        {
            if (OwningTabControl is null || !OwningTabControl.IsHandleCreated)
            {
                return null;
            }
 
            return direction switch
            {
                NavigateDirection.NavigateDirection_Parent => OwningTabControl.AccessibilityObject,
                NavigateDirection.NavigateDirection_NextSibling => OwningTabControl.AccessibilityObject.GetChild(GetChildId() + 1),
                NavigateDirection.NavigateDirection_PreviousSibling => OwningTabControl.AccessibilityObject.GetChild(GetChildId() - 1),
                _ => null
            };
        }
 
        // +1 is needed because 0 is the Pane id of the selected tab
        internal override int GetChildId() => CurrentIndex + 1;
 
        public override string? Help => GetHelpInternal().ToNullableStringAndFree();
 
        internal override BSTR GetHelpInternal() => SystemIAccessibleInternal.TryGetHelp(GetChildId());
 
        public override string? KeyboardShortcut => GetKeyboardShortcutInternal((VARIANT)GetChildId()).ToNullableStringAndFree();
 
        internal override BSTR GetKeyboardShortcutInternal(VARIANT childID) => SystemIAccessibleInternal.TryGetKeyboardShortcut(childID);
 
        internal override VARIANT GetPropertyValue(UIA_PROPERTY_ID propertyID)
            => propertyID switch
            {
                UIA_PROPERTY_ID.UIA_ControlTypePropertyId => (VARIANT)(int)UIA_CONTROLTYPE_ID.UIA_TabItemControlTypeId,
                UIA_PROPERTY_ID.UIA_HasKeyboardFocusPropertyId => (VARIANT)State.HasFlag(AccessibleStates.Focused),
                UIA_PROPERTY_ID.UIA_IsEnabledPropertyId => (VARIANT)(OwningTabControl?.Enabled ?? false),
                UIA_PROPERTY_ID.UIA_IsKeyboardFocusablePropertyId
                    // This is necessary for compatibility with MSAA proxy:
                    // IsKeyboardFocusable = true regardless the control is enabled/disabled.
                    => VARIANT.True,
                _ => base.GetPropertyValue(propertyID)
            };
 
        internal override bool IsPatternSupported(UIA_PATTERN_ID patternId)
            => patternId switch
            {
                // The "Enabled" property of the TabControl does not affect the behavior of that property,
                // so it is always true
                UIA_PATTERN_ID.UIA_SelectionItemPatternId => true,
                UIA_PATTERN_ID.UIA_InvokePatternId => false,
                UIA_PATTERN_ID.UIA_LegacyIAccessiblePatternId => true,
                _ => base.IsPatternSupported(patternId)
            };
 
        internal override void RemoveFromSelection()
        {
            // Do nothing, C++ implementation returns UIA_E_INVALIDOPERATION 0x80131509
        }
 
        internal override unsafe void SelectItem() => DoDefaultAction();
    }
}