File: System\Windows\Forms\Controls\ToolStrips\ToolStrip.ToolStripAccessibleObject.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.UI.Accessibility;
 
namespace System.Windows.Forms;
 
public partial class ToolStrip
{
    public class ToolStripAccessibleObject : ControlAccessibleObject
    {
        public ToolStripAccessibleObject(ToolStrip owner) : base(owner)
        {
        }
 
        internal override IRawElementProviderFragment.Interface? ElementProviderFromPoint(double x, double y)
            => this.IsOwnerHandleCreated(out ToolStrip? _) ? HitTest((int)x, (int)y) : null;
 
        /// <summary>
        ///  Return the child object at the given screen coordinates.
        /// </summary>
        public override AccessibleObject? HitTest(int x, int y)
        {
            if (!this.IsOwnerHandleCreated(out ToolStrip? owner))
            {
                return null;
            }
 
            Point clientHit = owner.PointToClient(new Point(x, y));
            ToolStripItem? item = owner.GetItemAt(clientHit);
            return ((item is not null) && (item.AccessibilityObject is not null))
                ? item.AccessibilityObject
                : base.HitTest(x, y);
        }
 
        /// <summary>
        ///  When overridden in a derived class, gets the accessible child corresponding to the specified
        ///  index.
        /// </summary>
        public override AccessibleObject? GetChild(int index)
        {
            if (!this.TryGetOwnerAs(out ToolStrip? owner) || owner.Items is null)
            {
                return null;
            }
 
            if (index == 0 && owner.Grip.Visible)
            {
                return owner.Grip.AccessibilityObject;
            }
            else if (owner.Grip.Visible && index > 0)
            {
                index--;
            }
 
            if (index < owner.Items.Count)
            {
                ToolStripItem? item = null;
                int myIndex = 0;
 
                // First we walk through the head aligned items.
                for (int i = 0; i < owner.Items.Count; ++i)
                {
                    if (owner.Items[i].Available && owner.Items[i].Alignment == ToolStripItemAlignment.Left)
                    {
                        if (myIndex == index)
                        {
                            item = owner.Items[i];
                            break;
                        }
 
                        myIndex++;
                    }
                }
 
                // If we didn't find it, then we walk through the tail aligned items.
                if (item is null)
                {
                    for (int i = 0; i < owner.Items.Count; ++i)
                    {
                        if (owner.Items[i].Available && owner.Items[i].Alignment == ToolStripItemAlignment.Right)
                        {
                            if (myIndex == index)
                            {
                                item = owner.Items[i];
                                break;
                            }
 
                            myIndex++;
                        }
                    }
                }
 
                if (item is null)
                {
                    Debug.Fail("No item matched the index??");
                    return null;
                }
 
                if (item.Placement == ToolStripItemPlacement.Overflow)
                {
                    return new ToolStripAccessibleObjectWrapperForItemsOnOverflow(item);
                }
 
                return item.AccessibilityObject;
            }
 
            if (owner.CanOverflow && owner.OverflowButton.Visible && index == owner.Items.Count)
            {
                return owner.OverflowButton.AccessibilityObject;
            }
 
            return null;
        }
 
        /// <summary>
        ///  When overridden in a derived class, gets the number of children
        ///  belonging to an accessible object.
        /// </summary>
        public override int GetChildCount()
        {
            if (!this.TryGetOwnerAs(out ToolStrip? owner) || owner.Items is null)
            {
                return -1;
            }
 
            int count = 0;
            for (int i = 0; i < owner.Items.Count; i++)
            {
                if (owner.Items[i].Available)
                {
                    count++;
                }
            }
 
            if (owner.Grip.Visible)
            {
                count++;
            }
 
            if (owner.CanOverflow && owner.OverflowButton.Visible)
            {
                count++;
            }
 
            return count;
        }
 
        internal AccessibleObject? GetChildFragment(int fragmentIndex, NavigateDirection direction, bool getOverflowItem = false)
        {
            if (!this.TryGetOwnerAs(out ToolStrip? owner) || fragmentIndex < 0)
            {
                return null;
            }
 
            ToolStripItemCollection items = getOverflowItem
                ? owner.OverflowItems
                : owner.DisplayedItems;
 
            if (!getOverflowItem
                && owner.CanOverflow
                && owner.OverflowButton.Visible
                && fragmentIndex == items.Count - 1)
            {
                return owner.OverflowButton.AccessibilityObject;
            }
 
            if (fragmentIndex < items.Count)
            {
                ToolStripItem item = items[fragmentIndex];
                if (item.Available)
                {
                    return GetItemAccessibleObject(item);
                }
            }
 
            return null;
 
            AccessibleObject? GetItemAccessibleObject(ToolStripItem item)
            {
                if (item is ToolStripControlHost controlHostItem and not ToolStripScrollButton)
                {
                    if (ShouldItemBeSkipped(controlHostItem.Control))
                    {
                        return GetFollowingChildFragment(fragmentIndex, items, direction);
                    }
 
                    return controlHostItem.ControlAccessibilityObject;
                }
 
                return item.AccessibilityObject;
            }
 
            bool ShouldItemBeSkipped(Control hostedControl)
                => hostedControl is null
                    || !hostedControl.SupportsUiaProviders
                    || (hostedControl is Label label && string.IsNullOrEmpty(label.Text));
 
            // Returns the next or the previous ToolStrip item, that is considered a valid navigation fragment
            //  (e.g. a control, that supports UIA providers and not a ToolStripControlHost).
            //  This method removes hosted ToolStrip items that are native controls
            //  (their accessible objects are provided by Windows), from the accessibility tree.
            //  It's necessary, because hosted native controls internally add accessible objects
            //  to the accessibility tree, and thus create duplicated. To avoid duplicates,
            //  remove hosted items with native accessibility objects from the tree.
            AccessibleObject? GetFollowingChildFragment(int index, ToolStripItemCollection items, NavigateDirection direction)
            {
                switch (direction)
                {
                    // "direction" is not used for navigation. This method is helper only.
                    // FirstChild, LastChild are used when searching non-native hosted child items of the ToolStrip.
                    // NextSibling, PreviousSibling are used when searching an item siblings.
                    case NavigateDirection.NavigateDirection_FirstChild:
                    case NavigateDirection.NavigateDirection_NextSibling:
                        for (int i = index + 1; i < items.Count; i++)
                        {
                            ToolStripItem item = items[i];
                            if (item is ToolStripControlHost controlHostItem)
                            {
                                if (ShouldItemBeSkipped(controlHostItem.Control))
                                {
                                    continue;
                                }
 
                                return controlHostItem.ControlAccessibilityObject;
                            }
 
                            return item.AccessibilityObject;
                        }
 
                        break;
 
                    case NavigateDirection.NavigateDirection_LastChild:
                    case NavigateDirection.NavigateDirection_PreviousSibling:
                        for (int i = index - 1; i >= 0; i--)
                        {
                            ToolStripItem item = items[i];
                            if (item is ToolStripControlHost controlHostItem)
                            {
                                if (ShouldItemBeSkipped(controlHostItem.Control))
                                {
                                    continue;
                                }
 
                                return controlHostItem.ControlAccessibilityObject;
                            }
 
                            return item.AccessibilityObject;
                        }
 
                        break;
                }
 
                return null;
            }
        }
 
        internal int GetChildOverflowFragmentCount()
        {
            if (!this.TryGetOwnerAs(out ToolStrip? owner) || owner.OverflowItems is null)
            {
                return -1;
            }
 
            return owner.OverflowItems.Count;
        }
 
        internal int GetChildFragmentCount()
        {
            if (!this.TryGetOwnerAs(out ToolStrip? owner) || owner.DisplayedItems is null)
            {
                return -1;
            }
 
            return owner.DisplayedItems.Count;
        }
 
        internal int GetChildFragmentIndex(ToolStripItem.ToolStripItemAccessibleObject child)
        {
            if (!this.TryGetOwnerAs(out ToolStrip? owner) || owner.Items is null)
            {
                return -1;
            }
 
            if (child.Owner == owner.Grip)
            {
                return 0;
            }
 
            ToolStripItemCollection items;
            ToolStripItemPlacement placement = child.Owner.Placement;
 
            if (owner is ToolStripOverflow overflow)
            {
                // Overflow items in ToolStripOverflow host are in DisplayedItems collection.
                items = overflow.DisplayedItems;
            }
            else
            {
                if (owner.CanOverflow && owner.OverflowButton.Visible && child.Owner == owner.OverflowButton)
                {
                    return GetChildFragmentCount() - 1;
                }
 
                // Items can be either in DisplayedItems or in OverflowItems (if overflow)
                items = placement == ToolStripItemPlacement.Main || child.Owner is ToolStripScrollButton
                    ? owner.DisplayedItems
                    : owner.OverflowItems;
            }
 
            for (int index = 0; index < items.Count; index++)
            {
                ToolStripItem item = items[index];
                if (item.Available && child.Owner == item)
                {
                    return index;
                }
            }
 
            return -1;
        }
 
        internal int GetChildIndex(ToolStripItem.ToolStripItemAccessibleObject child)
        {
            if (!this.TryGetOwnerAs(out ToolStrip? owner) || owner.Items is null)
            {
                return -1;
            }
 
            int index = 0;
            if (owner.Grip.Visible)
            {
                if (child.Owner == owner.Grip)
                {
                    return 0;
                }
 
                index = 1;
            }
 
            if (owner.CanOverflow && owner.OverflowButton.Visible && child.Owner == owner.OverflowButton)
            {
                return owner.Items.Count + index;
            }
 
            // First we walk through the head aligned items.
            for (int i = 0; i < owner.Items.Count; ++i)
            {
                if (owner.Items[i].Available && owner.Items[i].Alignment == ToolStripItemAlignment.Left)
                {
                    if (child.Owner == owner.Items[i])
                    {
                        return index;
                    }
 
                    index++;
                }
            }
 
            // If we didn't find it, then we walk through the tail aligned items.
            for (int i = 0; i < owner.Items.Count; ++i)
            {
                if (owner.Items[i].Available && owner.Items[i].Alignment == ToolStripItemAlignment.Right)
                {
                    if (child.Owner == owner.Items[i])
                    {
                        return index;
                    }
 
                    index++;
                }
            }
 
            return -1;
        }
 
        public override AccessibleRole Role => this.GetOwnerAccessibleRole(AccessibleRole.ToolBar);
 
        internal override IRawElementProviderFragmentRoot.Interface FragmentRoot => this;
 
        internal override IRawElementProviderFragment.Interface? FragmentNavigate(NavigateDirection direction)
        {
            if (!this.IsOwnerHandleCreated(out ToolStrip? _))
            {
                return null;
            }
 
            switch (direction)
            {
                case NavigateDirection.NavigateDirection_FirstChild:
                    int childCount = GetChildFragmentCount();
                    if (childCount > 0)
                    {
                        return GetChildFragment(0, direction);
                    }
 
                    break;
                case NavigateDirection.NavigateDirection_LastChild:
                    childCount = GetChildFragmentCount();
                    if (childCount > 0)
                    {
                        return GetChildFragment(childCount - 1, direction);
                    }
 
                    break;
            }
 
            return base.FragmentNavigate(direction);
        }
    }
}