File: System\Windows\Forms\Controls\ToolStrips\ToolStripDropDownMenu.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.Collections.Specialized;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms.Layout;
 
namespace System.Windows.Forms;
 
[Designer($"System.Windows.Forms.Design.ToolStripDropDownDesigner, {AssemblyRef.SystemDesign}")]
public partial class ToolStripDropDownMenu : ToolStripDropDown
{
    private Size _maxItemSize = Size.Empty;
    private Rectangle _checkRectangle = Rectangle.Empty;
    private Rectangle _imageRectangle = Rectangle.Empty;
    private Rectangle _arrowRectangle = Rectangle.Empty;
    private Rectangle _textRectangle = Rectangle.Empty;
    private Rectangle _imageMarginBounds = Rectangle.Empty;
    private int _paddingToTrim;
    private int _tabWidth = -1;
 
    private ToolStripScrollButton? _upScrollButton;
    private ToolStripScrollButton? _downScrollButton;
    private int _scrollAmount;
    private int _indexOfFirstDisplayedItem = -1;
 
    private BitVector32 _state;
 
    private static readonly int s_stateShowImageMargin = BitVector32.CreateMask();
    private static readonly int s_stateShowCheckMargin = BitVector32.CreateMask(s_stateShowImageMargin);
    private static readonly int s_stateMaxItemSizeValid = BitVector32.CreateMask(s_stateShowCheckMargin);
 
    private Size _defaultImageSize;
    private int _defaultImageMarginWidth;
    private int _defaultImageAndCheckMarginWidth;
    private Padding _imagePadding;
    private Padding _textPadding;
    private Padding _checkPadding;
    private Padding _arrowPadding;
    private int _arrowSize;
 
    public ToolStripDropDownMenu()
    {
        ScaleConstants(ScaleHelper.OneHundredPercentLogicalDpi);
    }
 
    /// <summary>
    ///  Constructor to autogenerate
    /// </summary>
    internal ToolStripDropDownMenu(ToolStripItem ownerItem, bool isAutoGenerated)
        : base(ownerItem, isAutoGenerated)
    {
        ScaleConstants(ScaleHelper.InitialSystemDpi);
    }
 
    protected override AccessibleObject CreateAccessibilityInstance()
        => new ToolStripDropDownMenuAccessibleObject(this);
 
    internal override bool AllItemsVisible
    {
        get => !RequiresScrollButtons;
        set => RequiresScrollButtons = !value;
    }
 
    internal Rectangle ArrowRectangle => _arrowRectangle;
 
    internal Rectangle CheckRectangle => _checkRectangle;
 
    protected override Padding DefaultPadding
    {
        get
        {
            RightToLeft rightToLeft = RightToLeft;
 
            int textPadding = (rightToLeft == RightToLeft.Yes) ? _textPadding.Right : _textPadding.Left;
            int padding = (ShowCheckMargin || ShowImageMargin) ? textPadding + ImageMargin.Width : textPadding;
 
            // scooch in all the items by the margin.
            if (rightToLeft == RightToLeft.Yes)
            {
                return new Padding(1, 2, padding, 2);
            }
 
            return new Padding(padding, 2, 1, 2);
        }
    }
 
    public override Rectangle DisplayRectangle
    {
        get
        {
            Rectangle rect = base.DisplayRectangle;
            if (GetToolStripState(STATE_SCROLLBUTTONS))
            {
                rect.Y += UpScrollButton.Height + UpScrollButton.Margin.Vertical;
                rect.Height -= UpScrollButton.Height + UpScrollButton.Margin.Vertical + DownScrollButton.Height + DownScrollButton.Margin.Vertical;
                // Because we're going to draw the scroll buttons on top of the padding, we need to add it back in here.
                rect = LayoutUtils.InflateRect(rect, new Padding(0, Padding.Top, 0, Padding.Bottom));
            }
 
            return rect;
        }
    }
 
    internal ToolStripScrollButton DownScrollButton
        => _downScrollButton ??= new ToolStripScrollButton(false) { ParentInternal = this };
 
    /// <summary>
    ///  the rectangle representing
    /// </summary>
    internal Rectangle ImageRectangle => _imageRectangle;
 
    internal int PaddingToTrim
    {
        get => _paddingToTrim;
 
        set
        {
            if (_paddingToTrim != value)
            {
                _paddingToTrim = value;
                AdjustSize();
            }
        }
    }
 
    /// <summary>
    ///  the rectangle representing the color stripe in the menu - this will appear as AffectedBounds
    ///  in the ToolStripRenderEventArgs
    /// </summary>
    internal Rectangle ImageMargin
    {
        get
        {
            _imageMarginBounds.Height = Height;
            return _imageMarginBounds;
        }
    }
 
    public override LayoutEngine LayoutEngine => ToolStripDropDownLayoutEngine.LayoutInstance;
 
    [DefaultValue(ToolStripLayoutStyle.Flow)]
    public new ToolStripLayoutStyle LayoutStyle
    {
        get => base.LayoutStyle;
        set => base.LayoutStyle = value;
    }
 
    protected internal override Size MaxItemSize
    {
        get
        {
            if (!_state[s_stateMaxItemSizeValid])
            {
                CalculateInternalLayoutMetrics();
            }
 
            return _maxItemSize;
        }
    }
 
    [DefaultValue(true)]
    [SRDescription(nameof(SR.ToolStripDropDownMenuShowImageMarginDescr))]
    [SRCategory(nameof(SR.CatAppearance))]
    public bool ShowImageMargin
    {
        get => _state[s_stateShowImageMargin];
 
        set
        {
            if (value != _state[s_stateShowImageMargin])
            {
                _state[s_stateShowImageMargin] = value;
                LayoutTransaction.DoLayout(this, this, PropertyNames.ShowImageMargin);
            }
        }
    }
 
    [DefaultValue(false)]
    [SRDescription(nameof(SR.ToolStripDropDownMenuShowCheckMarginDescr))]
    [SRCategory(nameof(SR.CatAppearance))]
    public bool ShowCheckMargin
    {
        get => _state[s_stateShowCheckMargin];
 
        set
        {
            if (value != _state[s_stateShowCheckMargin])
            {
                _state[s_stateShowCheckMargin] = value;
                LayoutTransaction.DoLayout(this, this, PropertyNames.ShowCheckMargin);
            }
        }
    }
 
    internal Rectangle TextRectangle
        => _textRectangle;
 
    internal ToolStripScrollButton UpScrollButton
        => _upScrollButton ??= new ToolStripScrollButton(true) { ParentInternal = this };
 
    /// <summary>
    ///  this takes a native menu and builds up a managed toolstrip around it.
    ///  Scenario: showing the items from the SystemMenu.
    ///  targetWindow is the window to send WM_COMMAND, WM_SYSCOMMAND to
    ///  hmenu is a handle to the native menu.
    /// </summary>
    internal static unsafe ToolStripDropDownMenu FromHMenu(HMENU hmenu, IWin32Window targetWindow)
    {
        ToolStripDropDownMenu managedDropDown = new();
        managedDropDown.SuspendLayout();
 
        int count = PInvoke.GetMenuItemCount(hmenu);
 
        ToolStripItem itemToAdd;
 
        // surf through the items in the collection, building up TSMenuItems and TSSeparators
        // corresponding to the native menu.
        for (uint i = 0; i < count; i++)
        {
            // peek at the i'th item.
            MENUITEMINFOW info = new()
            {
                cbSize = (uint)sizeof(MENUITEMINFOW),
                fMask = MENU_ITEM_MASK.MIIM_FTYPE
            };
 
            PInvoke.GetMenuItemInfo(hmenu, i, fByPosition: true, ref info);
 
            if (info.fType == MENU_ITEM_TYPE.MFT_SEPARATOR)
            {
                // its a separator.
                itemToAdd = new ToolStripSeparator();
            }
            else
            {
                // its a menu item... lets fish out the command id
                info = new()
                {
                    cbSize = (uint)sizeof(MENUITEMINFOW),
                    fMask = MENU_ITEM_MASK.MIIM_ID
                };
 
                PInvoke.GetMenuItemInfo(hmenu, i, fByPosition: true, ref info);
 
                // create the managed object - toolstripmenu item knows how to grok hmenu for information.
                itemToAdd = new ToolStripMenuItem(hmenu, (int)info.wID, targetWindow);
 
                // if there is a submenu fetch it.
                info = new()
                {
                    cbSize = (uint)sizeof(MENUITEMINFOW),
                    fMask = MENU_ITEM_MASK.MIIM_SUBMENU
                };
 
                PInvoke.GetMenuItemInfo(hmenu, i, fByPosition: true, ref info);
 
                if (!info.hSubMenu.IsNull)
                {
                    // set the dropdown to be the items from the submenu
                    ((ToolStripMenuItem)itemToAdd).DropDown = FromHMenu(info.hSubMenu, targetWindow);
                }
            }
 
            managedDropDown.Items.Add(itemToAdd);
        }
 
        managedDropDown.ResumeLayout();
        return managedDropDown;
    }
 
    private void CalculateInternalLayoutMetrics()
    {
        Size maxTextSize = Size.Empty;
        Size maxImageSize = Size.Empty;
        Size maxCheckSize = _defaultImageSize;
        Size maxArrowSize = Size.Empty;
        Size maxNonMenuItemSize = Size.Empty;
 
        // determine Text Metrics
        for (int i = 0; i < Items.Count; i++)
        {
            ToolStripItem item = Items[i];
 
            if (item is ToolStripMenuItem menuItem)
            {
                Size menuItemTextSize = menuItem.GetTextSize();
 
                if (menuItem.ShowShortcutKeys)
                {
                    Size shortcutTextSize = menuItem.GetShortcutTextSize();
                    if (_tabWidth == -1)
                    {
                        _tabWidth = TextRenderer.MeasureText("\t", Font).Width;
                    }
 
                    menuItemTextSize.Width += _tabWidth + shortcutTextSize.Width;
                    menuItemTextSize.Height = Math.Max(menuItemTextSize.Height, shortcutTextSize.Height);
                }
 
                // we truly only care about the maximum size we find.
                maxTextSize.Width = Math.Max(maxTextSize.Width, menuItemTextSize.Width);
                maxTextSize.Height = Math.Max(maxTextSize.Height, menuItemTextSize.Height);
 
                // determine Image Metrics
                Size imageSize = Size.Empty;
                if (menuItem.Image is not null)
                {
                    imageSize = (menuItem.ImageScaling == ToolStripItemImageScaling.SizeToFit) ? ImageScalingSize : menuItem.Image.Size;
                }
 
                maxImageSize.Width = Math.Max(maxImageSize.Width, imageSize.Width);
                maxImageSize.Height = Math.Max(maxImageSize.Height, imageSize.Height);
 
                if (menuItem.CheckedImage is not null)
                {
                    Size checkedImageSize = menuItem.CheckedImage.Size;
                    maxCheckSize.Width = Math.Max(checkedImageSize.Width, maxCheckSize.Width);
                    maxCheckSize.Height = Math.Max(checkedImageSize.Height, maxCheckSize.Height);
                }
            }
            else if (item is not ToolStripSeparator)
            {
                maxNonMenuItemSize.Height = Math.Max(item.Bounds.Height, maxNonMenuItemSize.Height);
                maxNonMenuItemSize.Width = Math.Max(item.Bounds.Width, maxNonMenuItemSize.Width);
            }
        }
 
        _maxItemSize.Height = Math.Max(maxTextSize.Height + _textPadding.Vertical, Math.Max(maxCheckSize.Height + _checkPadding.Vertical, maxArrowSize.Height + _arrowPadding.Vertical));
 
        if (ShowImageMargin)
        {
            // only add in the image into the calculation if we're going to render it.
            _maxItemSize.Height = Math.Max(maxImageSize.Height + _imagePadding.Vertical, _maxItemSize.Height);
        }
 
        // Always save space for an arrow
        maxArrowSize = new Size(_arrowSize, _maxItemSize.Height);
 
        maxTextSize.Height = _maxItemSize.Height - _textPadding.Vertical;
        maxImageSize.Height = _maxItemSize.Height - _imagePadding.Vertical;
        maxCheckSize.Height = _maxItemSize.Height - _checkPadding.Vertical;
 
        // fixup if there are non-menu items that are larger than our normal menu items
        maxTextSize.Width = Math.Max(maxTextSize.Width, maxNonMenuItemSize.Width);
 
        Point nextPoint = Point.Empty;
        int checkAndImageMarginWidth;
 
        int extraImageWidth = Math.Max(0, maxImageSize.Width - _defaultImageSize.Width);
 
        if (ShowCheckMargin && ShowImageMargin)
        {
            // double column - check margin then image margin
            // default to 46px - grow if necessary.
            checkAndImageMarginWidth = _defaultImageAndCheckMarginWidth;
 
            // add in the extra space for the image... since the check size is locked down to 16x16.
            checkAndImageMarginWidth += extraImageWidth;
 
            // align the checkmark
            nextPoint = new Point(_checkPadding.Left, _checkPadding.Top);
            _checkRectangle = LayoutUtils.Align(maxCheckSize, new Rectangle(nextPoint.X, nextPoint.Y, maxCheckSize.Width, _maxItemSize.Height), ContentAlignment.MiddleCenter);
 
            // align the image rectangle
            nextPoint.X = _checkRectangle.Right + _checkPadding.Right + _imagePadding.Left;
            nextPoint.Y = _imagePadding.Top;
            _imageRectangle = LayoutUtils.Align(maxImageSize, new Rectangle(nextPoint.X, nextPoint.Y, maxImageSize.Width, _maxItemSize.Height), ContentAlignment.MiddleCenter);
        }
        else if (ShowCheckMargin)
        {
            // no images should be shown in a ShowCheckMargin only scenario.
            // default to 24px - grow if necessary.
            checkAndImageMarginWidth = _defaultImageMarginWidth;
 
            // align the checkmark
            nextPoint = new Point(1, _checkPadding.Top);
            // nextPoint = new Point(scaledCheckPadding.Left, scaledCheckPadding.Top);
            _checkRectangle = LayoutUtils.Align(maxCheckSize, new Rectangle(nextPoint.X, nextPoint.Y, checkAndImageMarginWidth, _maxItemSize.Height), ContentAlignment.MiddleCenter);
 
            _imageRectangle = Rectangle.Empty;
        }
        else if (ShowImageMargin)
        {
            // checks and images render in the same area.
 
            // default to 24px - grow if necessary.
            checkAndImageMarginWidth = _defaultImageMarginWidth;
 
            // add in the extra space for the image... since the check size is locked down to 16x16.
            checkAndImageMarginWidth += extraImageWidth;
 
            // NOTE due to the Padding property, we're going to have to recalc the vertical alignment in ToolStripMenuItemInternalLayout.
            // Don't fuss here over the Y, X is what's critical.
 
            // check and image rect are the same - take the max of the image size and the check size and align
            nextPoint = new Point(1, _checkPadding.Top);
            _checkRectangle = LayoutUtils.Align(LayoutUtils.UnionSizes(maxCheckSize, maxImageSize), new Rectangle(nextPoint.X, nextPoint.Y, checkAndImageMarginWidth - 1, _maxItemSize.Height), ContentAlignment.MiddleCenter);
 
            // align the image
            _imageRectangle = _checkRectangle;
        }
        else
        {
            checkAndImageMarginWidth = 0;
        }
 
        nextPoint.X = checkAndImageMarginWidth + 1;
 
        // calculate space for image
        // if we didnt have a check - make sure to ignore check padding
 
        // consider: should we constrain to a reasonable width?
        // imageMarginBounds = new Rectangle(0, 0, Math.Max(imageMarginWidth,DefaultImageMarginWidth), this.Height);
        _imageMarginBounds = new Rectangle(0, 0, checkAndImageMarginWidth, Height);
 
        // calculate space for shortcut and text
        nextPoint.X = _imageMarginBounds.Right + _textPadding.Left;
        nextPoint.Y = _textPadding.Top;
        _textRectangle = new Rectangle(nextPoint, maxTextSize);
 
        // calculate space for arrow
        nextPoint.X = _textRectangle.Right + _textPadding.Right + _arrowPadding.Left;
        nextPoint.Y = _arrowPadding.Top;
        _arrowRectangle = new Rectangle(nextPoint, maxArrowSize);
 
        // calculate space required for all of these pieces
        _maxItemSize.Width = (_arrowRectangle.Right + _arrowPadding.Right) - _imageMarginBounds.Left;
 
        Padding = DefaultPadding;
        int trimPadding = _imageMarginBounds.Width;
 
        if (RightToLeft == RightToLeft.Yes)
        {
            // reverse the rectangle alignment in RightToLeft.Yes
            trimPadding += _textPadding.Right;
            int width = _maxItemSize.Width;
            _checkRectangle.X = width - _checkRectangle.Right;
            _imageRectangle.X = width - _imageRectangle.Right;
            _textRectangle.X = width - _textRectangle.Right;
            _arrowRectangle.X = width - _arrowRectangle.Right;
            _imageMarginBounds.X = width - _imageMarginBounds.Right;
        }
        else
        {
            trimPadding += _textPadding.Left;
        }
 
        // We need to make sure that the text really appears vertically centered - this can be a problem in
        // systems which force the text rectangle to be odd.
 
        // force this to be an even height.
        _maxItemSize.Height += _maxItemSize.Height % 2;
 
        _textRectangle.Y = LayoutUtils.VAlign(_textRectangle.Size, new Rectangle(Point.Empty, _maxItemSize), ContentAlignment.MiddleCenter).Y;
        _textRectangle.Y += (_textRectangle.Height % 2); // if the height is odd, push down by one px
        _state[s_stateMaxItemSizeValid] = true;
        PaddingToTrim = trimPadding;
    }
 
    internal override void ChangeSelection(ToolStripItem? nextItem)
    {
        if (nextItem is not null)
        {
            Rectangle displayRect = DisplayRectangle;
            if (!displayRect.Contains(displayRect.X, nextItem.Bounds.Top)
                || !displayRect.Contains(displayRect.X, nextItem.Bounds.Bottom))
            {
                int delta;
                if (displayRect.Y > nextItem.Bounds.Top)
                {
                    delta = nextItem.Bounds.Top - displayRect.Y;
                }
                else
                {
                    delta = nextItem.Bounds.Bottom - (displayRect.Y + displayRect.Height);
                    // Now adjust so that the item at the top isn't truncated.
                    int index = Items.IndexOf(nextItem);
                    while (index >= 0)
                    {
                        // we need to roll back to the index which is visible
                        if ((Items[index].Visible && displayRect.Contains(displayRect.X, Items[index].Bounds.Top - delta))
                            || !Items[index].Visible)
                        {
                            --index;
                        }
                        else
                        {
                            break;
                        }
                    }
 
                    if (index >= 0)
                    {
                        if (displayRect.Contains(displayRect.X, Items[index].Bounds.Bottom - delta))
                        {
                            // We found an item which is truncated at the top.
                            delta += (Items[index].Bounds.Bottom - delta) - displayRect.Top;
                        }
                    }
                }
 
                ScrollInternal(delta);
                UpdateScrollButtonStatus();
            }
        }
 
        base.ChangeSelection(nextItem);
    }
 
    protected internal override ToolStripItem CreateDefaultItem(string? text, Image? image, EventHandler? onClick)
    {
        if (text == "-")
        {
            return new ToolStripSeparator();
        }
        else
        {
            return new ToolStripMenuItem(text, image, onClick);
        }
    }
 
    internal override ToolStripItem? GetNextItem(ToolStripItem? start, ArrowDirection direction, bool rtlAware)
    {
        // for up/down we don't care about flipping left/right tab should still take you down.
        return GetNextItem(start, direction);
    }
 
    internal override void Initialize()
    {
        base.Initialize();
        Padding = DefaultPadding;
        _state[s_stateShowImageMargin] = true;
    }
 
    protected override void OnLayout(LayoutEventArgs e)
    {
        if (!IsDisposed)
        {
            // We always layout as if we don't need scroll buttons.
            // If we do, then we'll adjust the positions to match.
            RequiresScrollButtons = false;
            CalculateInternalLayoutMetrics();
            base.OnLayout(e);
            if (!RequiresScrollButtons)
            {
                ResetScrollPosition();
            }
        }
    }
 
    protected override void OnFontChanged(EventArgs e)
    {
        _tabWidth = -1;
        base.OnFontChanged(e);
    }
 
    protected override void OnPaintBackground(PaintEventArgs e)
    {
        base.OnPaintBackground(e);
        if (ShowCheckMargin || ShowImageMargin)
        {
            Renderer.DrawImageMargin(new ToolStripRenderEventArgs(e.Graphics, this, ImageMargin, SystemColors.Control));
        }
    }
 
    internal override void ReleaseToolStripItemsProviders(ToolStripItemCollection items)
    {
    }
 
    internal override void ResetScaling(int newDpi)
    {
        base.ResetScaling(newDpi);
        CommonProperties.xClearPreferredSizeCache(this);
        ScaleConstants(newDpi);
    }
 
    private void ScaleConstants(int dpi)
    {
        const int LogicalDefaultImageSize = 16;
        const int LogicalImagePadding = 2;
        const int LogicalArrowSize = 10;
 
        // If we have an image or check margin with no image or checks in it use this - which is consistent with Office
        // and an image margin with a 16x16 icon in it.
        const int DefaultImageMarginWidth = 24;
        const int DefaultImageAndCheckMarginWidth = 46;
 
        int defaultImageSize = ScaleHelper.ScaleToDpi(LogicalDefaultImageSize, dpi);
        _defaultImageSize = new(defaultImageSize, defaultImageSize);
 
        // Add an additional pixel for the border after scaling for the next two values.
        _defaultImageMarginWidth = ScaleHelper.ScaleToDpi(DefaultImageMarginWidth, dpi) + 1;
        _defaultImageAndCheckMarginWidth = ScaleHelper.ScaleToDpi(DefaultImageAndCheckMarginWidth, dpi) + 1;
 
        _imagePadding = new(ScaleHelper.ScaleToDpi(LogicalImagePadding, dpi));
        _textPadding = ScaleHelper.ScaleToDpi(new Padding(8, 1, 9, 1), dpi);
        _checkPadding = ScaleHelper.ScaleToDpi(new Padding(5, 2, 2, 2), dpi);
        _arrowPadding = ScaleHelper.ScaleToDpi(new Padding(0, 0, 8, 0), dpi);
        _arrowSize = ScaleHelper.ScaleToDpi(LogicalArrowSize, dpi);
    }
 
    internal override bool RequiresScrollButtons
    {
        get
        {
            return GetToolStripState(STATE_SCROLLBUTTONS);
        }
        set
        {
            bool changed = (RequiresScrollButtons != value);
            SetToolStripState(STATE_SCROLLBUTTONS, value);
            if (changed)
            {
                UpdateScrollButtonLocations();
                if (Items.Count > 0)
                {
                    int delta = Items[0].Bounds.Top - DisplayRectangle.Top;
                    ScrollInternal(delta);
                    _scrollAmount -= delta;
                    if (value)
                    {
                        RestoreScrollPosition();
                    }
                }
                else
                {
                    _scrollAmount = 0;
                }
            }
        }
    }
 
    internal void ResetScrollPosition()
        => _scrollAmount = 0;
 
    internal void RestoreScrollPosition()
    {
        if (!RequiresScrollButtons || Items.Count == 0)
        {
            return;
        }
 
        // We don't just scroll by the amount, because that might
        // cause the bottom of the menu to be blank if some items have
        // been removed/hidden since the last time we were displayed.
        // This also deals with items of different height, so that we don't truncate
        // and items under the top scrollbar.
 
        Rectangle displayRectangle = DisplayRectangle;
        int alreadyScrolled = displayRectangle.Top - Items[0].Bounds.Top;
 
        int requiredScrollAmount = _scrollAmount - alreadyScrolled;
 
        int deltaToScroll = 0;
        if (requiredScrollAmount > 0)
        {
            for (int i = 0; i < Items.Count && deltaToScroll < requiredScrollAmount; ++i)
            {
                if (Items[i].Available)
                {
                    Rectangle adjustedLastItemBounds = Items[Items.Count - 1].Bounds;
                    adjustedLastItemBounds.Y -= deltaToScroll;
                    if (displayRectangle.Contains(displayRectangle.X, adjustedLastItemBounds.Top)
                        && displayRectangle.Contains(displayRectangle.X, adjustedLastItemBounds.Bottom))
                    {
                        // Scrolling this amount would make the last item visible, so don't scroll any more.
                        break;
                    }
 
                    // We use a delta between the tops, since it takes margin's and padding into account.
                    if (i < Items.Count - 1)
                    {
                        deltaToScroll += Items[i + 1].Bounds.Top - Items[i].Bounds.Top;
                    }
                    else
                    {
                        deltaToScroll += Items[i].Bounds.Height;
                    }
                }
            }
        }
        else
        {
            for (int i = Items.Count - 1; i >= 0 && deltaToScroll > requiredScrollAmount; --i)
            {
                if (Items[i].Available)
                {
                    Rectangle adjustedLastItemBounds = Items[0].Bounds;
                    adjustedLastItemBounds.Y -= deltaToScroll;
                    if (displayRectangle.Contains(displayRectangle.X, adjustedLastItemBounds.Top)
                        && displayRectangle.Contains(displayRectangle.X, adjustedLastItemBounds.Bottom))
                    {
                        // Scrolling this amount would make the last item visible, so don't scroll any more.
                        break;
                    }
 
                    // We use a delta between the tops, since it takes margin's and padding into account.
                    if (i > 0)
                    {
                        deltaToScroll -= Items[i].Bounds.Top - Items[i - 1].Bounds.Top;
                    }
                    else
                    {
                        deltaToScroll -= Items[i].Bounds.Height;
                    }
                }
            }
        }
 
        ScrollInternal(deltaToScroll);
        _scrollAmount = DisplayRectangle.Top - Items[0].Bounds.Top;
        UpdateScrollButtonLocations();
    }
 
    internal override void ScrollInternal(int delta)
    {
        base.ScrollInternal(delta);
        _scrollAmount += delta;
    }
 
    internal void ScrollInternal(bool up)
    {
        UpdateScrollButtonStatus();
 
        // calling this to get ScrollWindowEx. In actuality it does nothing
        // to change the display rect!
        int delta;
        if (_indexOfFirstDisplayedItem == -1 || _indexOfFirstDisplayedItem >= Items.Count)
        {
            Debug.Fail("Why wasn't 'UpdateScrollButtonStatus called'? We don't have the item to scroll by");
            int menuHeight = SystemInformation.MenuHeight;
 
            delta = up ? -menuHeight : menuHeight;
        }
        else
        {
            if (up)
            {
                if (_indexOfFirstDisplayedItem == 0)
                {
                    Debug.Fail("We're trying to scroll up, but the top item is displayed!!!");
                    delta = 0;
                }
                else
                {
                    ToolStripItem itemTop = Items[_indexOfFirstDisplayedItem - 1];
                    ToolStripItem itemBottom = Items[_indexOfFirstDisplayedItem];
                    // We use a delta between the tops, since it takes margin's and padding into account.
                    delta = itemTop.Bounds.Top - itemBottom.Bounds.Top;
                }
            }
            else
            {
                if (_indexOfFirstDisplayedItem == Items.Count - 1)
                {
                    Debug.Fail("We're trying to scroll down, but the top item is displayed!!!");
                }
 
                ToolStripItem itemTop = Items[_indexOfFirstDisplayedItem];
                ToolStripItem itemBottom = Items[_indexOfFirstDisplayedItem + 1];
                // We use a delta between the tops, since it takes margin's and padding into account.
                delta = itemBottom.Bounds.Top - itemTop.Bounds.Top;
            }
        }
 
        ScrollInternal(delta);
        UpdateScrollButtonLocations();
    }
 
    protected override void SetDisplayedItems()
    {
        base.SetDisplayedItems();
        if (RequiresScrollButtons)
        {
            DisplayedItems.Insert(0, UpScrollButton);
            DisplayedItems.Add(DownScrollButton);
            UpdateScrollButtonLocations();
            UpScrollButton.Visible = true;
            DownScrollButton.Visible = true;
        }
        else
        {
            UpScrollButton.Visible = false;
            DownScrollButton.Visible = false;
        }
    }
 
    private void UpdateScrollButtonLocations()
    {
        if (GetToolStripState(STATE_SCROLLBUTTONS))
        {
            Size upSize = UpScrollButton.GetPreferredSize(Size.Empty);
            //
            Point upLocation = new(1, 0);
 
            UpScrollButton.SetBounds(new Rectangle(upLocation, upSize));
 
            Size downSize = DownScrollButton.GetPreferredSize(Size.Empty);
            int height = GetDropDownBounds(Bounds).Height;
 
            Point downLocation = new(1, height - downSize.Height);
            DownScrollButton.SetBounds(new Rectangle(downLocation, downSize));
            UpdateScrollButtonStatus();
        }
    }
 
    private void UpdateScrollButtonStatus()
    {
        Rectangle displayRectangle = DisplayRectangle;
 
        _indexOfFirstDisplayedItem = -1;
        int minY = int.MaxValue, maxY = 0;
 
        for (int i = 0; i < Items.Count; ++i)
        {
            ToolStripItem item = Items[i];
            if (UpScrollButton == item)
            {
                continue;
            }
 
            if (DownScrollButton == item)
            {
                continue;
            }
 
            if (!item.Available)
            {
                continue;
            }
 
            if (_indexOfFirstDisplayedItem == -1 && displayRectangle.Contains(displayRectangle.X, item.Bounds.Top))
            {
                _indexOfFirstDisplayedItem = i;
            }
 
            minY = Math.Min(minY, item.Bounds.Top);
            maxY = Math.Max(maxY, item.Bounds.Bottom);
        }
 
        UpScrollButton.Enabled = !displayRectangle.Contains(displayRectangle.X, minY);
        DownScrollButton.Enabled = !displayRectangle.Contains(displayRectangle.X, maxY);
    }
}