File: System\Windows\Forms\Controls\ToolStrips\ToolStripItem.ToolStripItemAccessibleObject.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 System.Globalization;
using Windows.Win32.System.Variant;
using Windows.Win32.UI.Accessibility;
using PInvoke = Windows.Win32.PInvoke;
 
namespace System.Windows.Forms;
 
public abstract partial class ToolStripItem
{
    /// <summary>
    ///  An implementation of AccessibleChild for use with ToolStripItems
    /// </summary>
    public class ToolStripItemAccessibleObject : AccessibleObject
    {
        private readonly ToolStripItem _ownerItem; // The associated ToolStripItem for this AccessibleChild (if any)
 
        private AccessibleStates _additionalState = AccessibleStates.None; // Test hook for the designer
 
        private int[]? _runtimeId;
 
        public ToolStripItemAccessibleObject(ToolStripItem ownerItem)
        {
            _ownerItem = ownerItem.OrThrowIfNull();
        }
 
        public override string DefaultAction
        {
            get
            {
                string? defaultAction = _ownerItem.AccessibleDefaultActionDescription;
                if (defaultAction is not null)
                {
                    return defaultAction;
                }
 
                return SR.AccessibleActionPress;
            }
        }
 
        internal override bool CanGetDefaultActionInternal => false;
 
        public override string? Description =>
            _ownerItem.AccessibleDescription is { } description ? description : base.Description;
 
        internal override bool CanGetDescriptionInternal => IsInternal && _ownerItem.AccessibleDescription is null;
 
        public override string? Help
        {
            get
            {
                QueryAccessibilityHelpEventHandler? handler = (QueryAccessibilityHelpEventHandler?)Owner.Events[s_queryAccessibilityHelpEvent];
                if (handler is not null)
                {
                    QueryAccessibilityHelpEventArgs args = new();
                    handler(Owner, args);
                    return args.HelpString;
                }
 
                return base.Help;
            }
        }
 
        internal override bool CanGetHelpInternal
            => IsInternal && (QueryAccessibilityHelpEventHandler?)Owner.Events[s_queryAccessibilityHelpEvent] is null;
 
        public override string KeyboardShortcut
        {
            get
            {
                // This really is the Mnemonic - NOT the shortcut. E.g. in notepad Edit->Replace is Control+H
                // but the KeyboardShortcut comes up as the mnemonic 'r'.
                char mnemonic = WindowsFormsUtils.GetMnemonic(_ownerItem.Text, false);
                if (_ownerItem.IsOnDropDown)
                {
                    // no ALT on dropdown
                    return mnemonic == '\0' ? string.Empty : mnemonic.ToString();
                }
 
                return mnemonic == '\0' ? string.Empty : $"Alt+{mnemonic}";
            }
        }
 
        internal override bool CanGetKeyboardShortcutInternal => false;
 
        /// <remarks>
        ///  <para>
        ///    First item should be <see cref="PInvoke.UiaAppendRuntimeId" /> since this is not a top-level
        ///    element of the fragment. Second item can be anything, but here it is the owner hash code.
        ///    For <see cref="ToolStrip" /> hash code is unique even with child controls.
        ///    <see cref="Control.Handle" /> is not.
        ///  </para>
        /// </remarks>
        /// <inheritdoc cref="AccessibleObject.RuntimeId" />
        internal override int[] RuntimeId => _runtimeId ??=
        [
            (int)PInvoke.UiaAppendRuntimeId,
            _ownerItem.GetHashCode()
        ];
 
        internal override VARIANT GetPropertyValue(UIA_PROPERTY_ID propertyID) =>
            propertyID switch
            {
                // "ControlType" value depends on owner's AccessibleRole value.
                // See: docs/accessibility/accessible-role-controltype.md
                UIA_PROPERTY_ID.UIA_ControlTypePropertyId => (VARIANT)(int)AccessibleRoleControlTypeMap.GetControlType(Role),
                UIA_PROPERTY_ID.UIA_HasKeyboardFocusPropertyId => (VARIANT)_ownerItem.Selected,
                UIA_PROPERTY_ID.UIA_IsEnabledPropertyId => (VARIANT)_ownerItem.Enabled,
                UIA_PROPERTY_ID.UIA_IsKeyboardFocusablePropertyId => (VARIANT)_ownerItem.CanSelect,
                UIA_PROPERTY_ID.UIA_IsOffscreenPropertyId => (VARIANT)GetIsOffscreenPropertyValue(_ownerItem.Placement, Bounds),
                UIA_PROPERTY_ID.UIA_IsControlElementPropertyId => VARIANT.True,
                UIA_PROPERTY_ID.UIA_IsContentElementPropertyId => VARIANT.True,
                _ => base.GetPropertyValue(propertyID)
            };
 
        public override string? Name
        {
            get
            {
                string? name = _ownerItem.AccessibleName;
                if (name is not null)
                {
                    return name;
                }
 
                string? baseName = base.Name;
                if (string.IsNullOrEmpty(baseName))
                {
                    return WindowsFormsUtils.TextWithoutMnemonics(_ownerItem.Text)!;
                }
 
                return baseName;
            }
            set => _ownerItem.AccessibleName = value;
        }
 
        internal override bool CanGetNameInternal => false;
 
        internal override bool CanSetNameInternal => false;
 
        internal ToolStripItem Owner => _ownerItem;
 
        public override AccessibleRole Role
        {
            get
            {
                AccessibleRole role = _ownerItem.AccessibleRole;
                if (role != AccessibleRole.Default)
                {
                    return role;
                }
 
                return AccessibleRole.PushButton;
            }
        }
 
        public override AccessibleStates State
        {
            get
            {
                if (!_ownerItem.CanSelect)
                {
                    return base.State | _additionalState;
                }
 
                if (!_ownerItem.Enabled)
                {
                    if (_ownerItem.Selected && _ownerItem is ToolStripMenuItem)
                    {
                        return AccessibleStates.Unavailable | _additionalState | AccessibleStates.Focused;
                    }
 
                    // Disabled menu items that are selected must have focus
                    // state so that Narrator can announce them.
                    if (_ownerItem.Selected && _ownerItem is ToolStripMenuItem)
                    {
                        return AccessibleStates.Focused;
                    }
 
                    return AccessibleStates.Unavailable | _additionalState;
                }
 
                AccessibleStates accState = AccessibleStates.Focusable | _additionalState;
                if (_ownerItem.Selected || _ownerItem.Pressed)
                {
                    accState |= AccessibleStates.Focused | AccessibleStates.HotTracked;
                }
 
                if (_ownerItem.Pressed)
                {
                    accState |= AccessibleStates.Pressed;
                }
 
                return accState;
            }
        }
 
        public override void DoDefaultAction()
        {
            Owner?.PerformClick();
        }
 
        public override int GetHelpTopic(out string? fileName)
        {
            int topic = 0;
 
            QueryAccessibilityHelpEventHandler? handler = (QueryAccessibilityHelpEventHandler?)Owner.Events[s_queryAccessibilityHelpEvent];
 
            if (handler is not null)
            {
                QueryAccessibilityHelpEventArgs args = new();
                handler(Owner, args);
 
                fileName = args.HelpNamespace;
 
                int.TryParse(args.HelpKeyword, NumberStyles.Integer, CultureInfo.InvariantCulture, out topic);
 
                return topic;
            }
 
            return base.GetHelpTopic(out fileName);
        }
 
        internal override bool CanGetHelpTopicInternal => IsInternal && Owner.Events[s_queryAccessibilityHelpEvent] is null;
 
        public override AccessibleObject? Navigate(AccessibleNavigation navigationDirection)
        {
            ToolStripItem? nextItem = null;
 
            if (Owner is not null)
            {
                ToolStrip? parent = Owner.ParentInternal;
                if (parent is null)
                {
                    return null;
                }
 
                switch (navigationDirection)
                {
                    case AccessibleNavigation.FirstChild:
                        nextItem = parent.GetNextItem(null, ArrowDirection.Right, /*RTLAware=*/true);
                        break;
                    case AccessibleNavigation.LastChild:
                        nextItem = parent.GetNextItem(null, ArrowDirection.Left, /*RTLAware=*/true);
                        break;
                    case AccessibleNavigation.Previous:
                    case AccessibleNavigation.Left:
                        nextItem = parent.GetNextItem(Owner, ArrowDirection.Left, /*RTLAware=*/true);
                        break;
                    case AccessibleNavigation.Next:
                    case AccessibleNavigation.Right:
                        nextItem = parent.GetNextItem(Owner, ArrowDirection.Right, /*RTLAware=*/true);
                        break;
                    case AccessibleNavigation.Up:
                        nextItem = (Owner.IsOnDropDown) ? parent.GetNextItem(Owner, ArrowDirection.Up) :
                                                           parent.GetNextItem(Owner, ArrowDirection.Left, /*RTLAware=*/true);
                        break;
                    case AccessibleNavigation.Down:
                        nextItem = (Owner.IsOnDropDown) ? parent.GetNextItem(Owner, ArrowDirection.Down) :
                                                           parent.GetNextItem(Owner, ArrowDirection.Right, /*RTLAware=*/true);
                        break;
                }
            }
 
            return nextItem?.AccessibilityObject;
        }
 
        public void AddState(AccessibleStates state)
        {
            if (state == AccessibleStates.None)
            {
                _additionalState = state;
            }
            else
            {
                _additionalState |= state;
            }
        }
 
        public override string ToString()
        {
            if (Owner is not null)
            {
                return $"ToolStripItemAccessibleObject: Owner = {Owner}";
            }
 
            return "ToolStripItemAccessibleObject: Owner = null";
        }
 
        /// <summary>
        ///  Gets the bounds of the accessible object, in screen coordinates.
        /// </summary>
        public override Rectangle Bounds
        {
            get
            {
                Rectangle bounds = Owner.Bounds;
 
                if (Owner.ParentInternal is not null && Owner.ParentInternal.Visible)
                {
                    return new Rectangle(Owner.ParentInternal.PointToScreen(bounds.Location), bounds.Size);
                }
 
                return Rectangle.Empty;
            }
        }
 
        /// <summary>
        ///  When overridden in a derived class, gets or sets the parent of an accessible object.
        /// </summary>
        public override AccessibleObject? Parent
        {
            get
            {
                if (Owner.IsOnDropDown)
                {
                    // Return the owner item as the accessible parent.
                    ToolStripDropDown dropDown = Owner.GetCurrentParentDropDown()!;
                    return dropDown.AccessibilityObject;
                }
 
                return (Owner.Parent is not null) ? Owner.Parent.AccessibilityObject : base.Parent;
            }
        }
 
        internal override IRawElementProviderFragmentRoot.Interface? FragmentRoot =>
            _ownerItem.RootToolStrip?.AccessibilityObject;
 
        internal override IRawElementProviderFragment.Interface? FragmentNavigate(NavigateDirection direction)
        {
            switch (direction)
            {
                case NavigateDirection.NavigateDirection_Parent:
                    return Parent;
                case NavigateDirection.NavigateDirection_NextSibling:
                case NavigateDirection.NavigateDirection_PreviousSibling:
                    int index = GetChildFragmentIndex();
                    if (index == -1)
                    {
                        Debug.Fail("No item matched the index?");
                        return null;
                    }
 
                    AccessibleObject? sibling = null;
                    index += direction == NavigateDirection.NavigateDirection_NextSibling ? 1 : -1;
                    int itemsCount = GetChildFragmentCount();
                    if (index >= 0 && index < itemsCount)
                    {
                        sibling = GetChildFragment(index, direction);
                    }
 
                    return sibling;
            }
 
            return base.FragmentNavigate(direction);
        }
 
        private AccessibleObject? GetChildFragment(int index, NavigateDirection direction)
        {
            if (Parent is ToolStrip.ToolStripAccessibleObject toolStripParent)
            {
                return toolStripParent.GetChildFragment(index, direction);
            }
 
            // ToolStripOverflowButtonAccessibleObject is derived from ToolStripDropDownItemAccessibleObject
            // and we should not process ToolStripOverflowButton as a ToolStripDropDownItem here so check for
            // the ToolStripOverflowButton firstly as more specific condition.
            if (Parent is ToolStripOverflowButton.ToolStripOverflowButtonAccessibleObject toolStripOverflowButtonParent)
            {
                if (toolStripOverflowButtonParent.Parent is ToolStrip.ToolStripAccessibleObject toolStripGrandParent)
                {
                    return toolStripGrandParent.GetChildFragment(index, direction, true);
                }
            }
 
            if (Parent is ToolStripDropDownItemAccessibleObject dropDownItemParent)
            {
                return dropDownItemParent.GetChildFragment(index, direction);
            }
 
            return null;
        }
 
        private int GetChildFragmentCount()
        {
            if (Parent is ToolStrip.ToolStripAccessibleObject toolStripParent)
            {
                return toolStripParent.GetChildFragmentCount();
            }
 
            if (Parent is ToolStripOverflowButton.ToolStripOverflowButtonAccessibleObject toolStripOverflowButtonParent)
            {
                if (toolStripOverflowButtonParent.Parent is ToolStrip.ToolStripAccessibleObject toolStripGrandParent)
                {
                    return toolStripGrandParent.GetChildOverflowFragmentCount();
                }
            }
 
            if (Parent is ToolStripDropDownItemAccessibleObject dropDownItemParent)
            {
                return dropDownItemParent.GetChildCount();
            }
 
            return -1;
        }
 
        private int GetChildFragmentIndex()
        {
            if (Parent is ToolStrip.ToolStripAccessibleObject toolStripParent)
            {
                return toolStripParent.GetChildFragmentIndex(this);
            }
 
            if (Parent is ToolStripOverflowButton.ToolStripOverflowButtonAccessibleObject toolStripOverflowButtonParent)
            {
                if (toolStripOverflowButtonParent.Parent is ToolStrip.ToolStripAccessibleObject toolStripGrandParent)
                {
                    return toolStripGrandParent.GetChildFragmentIndex(this);
                }
            }
 
            if (Parent is ToolStripDropDownItemAccessibleObject dropDownItemParent)
            {
                return dropDownItemParent.GetChildFragmentIndex(this);
            }
 
            return -1;
        }
 
        internal override bool IsPatternSupported(UIA_PATTERN_ID patternId)
        {
            if (patternId == UIA_PATTERN_ID.UIA_LegacyIAccessiblePatternId)
            {
                return true;
            }
 
            return base.IsPatternSupported(patternId);
        }
 
        internal override void SetFocus() => Owner.Select();
 
        internal void RaiseFocusChanged()
        {
            ToolStrip? root = _ownerItem.RootToolStrip;
            if (root is not null && root.IsHandleCreated && root.SupportsUiaProviders)
            {
                RaiseAutomationEvent(UIA_EVENT_ID.UIA_AutomationFocusChangedEventId);
            }
        }
    }
}