File: System\ComponentModel\Design\CollectionEditor.SplitButton.cs
Web Access
Project: src\src\System.Windows.Forms.Design\src\System.Windows.Forms.Design.csproj (System.Windows.Forms.Design)
// 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.Windows.Forms;
using System.Windows.Forms.VisualStyles;
 
namespace System.ComponentModel.Design;
 
public partial class CollectionEditor
{
    internal class SplitButton : Button
    {
        private PushButtonState _state;
        private const int PushButtonWidth = 14;
        private Rectangle _dropDownRectangle;
        private bool _showSplit;
 
        private static bool s_isScalingInitialized;
        private const int Offset2Pixels = 2;
        private static int s_offset2X = Offset2Pixels;
        private static int s_offset2Y = Offset2Pixels;
 
        public SplitButton() : base()
        {
            if (!s_isScalingInitialized)
            {
                s_offset2X = ScaleHelper.ScaleToInitialSystemDpi(Offset2Pixels);
                s_offset2Y = ScaleHelper.ScaleToInitialSystemDpi(Offset2Pixels);
                s_isScalingInitialized = true;
            }
        }
 
        public bool ShowSplit
        {
            set
            {
                if (value != _showSplit)
                {
                    _showSplit = value;
                    Invalidate();
                }
            }
        }
 
        private PushButtonState State
        {
            get => _state;
            set
            {
                if (!_state.Equals(value))
                {
                    _state = value;
                    Invalidate();
                }
            }
        }
 
        public override Size GetPreferredSize(Size proposedSize)
        {
            Size preferredSize = base.GetPreferredSize(proposedSize);
            if (_showSplit && !string.IsNullOrEmpty(Text) && TextRenderer.MeasureText(Text, Font).Width + PushButtonWidth > preferredSize.Width)
            {
                return preferredSize + new Size(PushButtonWidth, 0);
            }
 
            return preferredSize;
        }
 
        protected override bool IsInputKey(Keys keyData)
        {
            if (keyData is Keys.Down && _showSplit)
            {
                return true;
            }
 
            return base.IsInputKey(keyData);
        }
 
        protected override void OnGotFocus(EventArgs e)
        {
            if (!_showSplit)
            {
                base.OnGotFocus(e);
                return;
            }
 
            if (State is not (PushButtonState.Pressed or PushButtonState.Disabled))
            {
                State = PushButtonState.Default;
            }
        }
 
        protected override void OnKeyDown(KeyEventArgs kevent)
        {
            if (kevent.KeyCode is Keys.Down && _showSplit)
            {
                ShowContextMenuStrip();
            }
            else
            {
                // We need to pass the unhandled characters (including Keys.Space) on
                // to base.OnKeyDown when it's not to drop the split menu
                base.OnKeyDown(kevent);
            }
        }
 
        protected override void OnLostFocus(EventArgs e)
        {
            if (!_showSplit)
            {
                base.OnLostFocus(e);
                return;
            }
 
            if (State is not (PushButtonState.Pressed or PushButtonState.Disabled))
            {
                State = PushButtonState.Normal;
            }
        }
 
        protected override void OnMouseDown(MouseEventArgs e)
        {
            if (!_showSplit)
            {
                base.OnMouseDown(e);
                return;
            }
 
            if (_dropDownRectangle.Contains(e.Location))
            {
                ShowContextMenuStrip();
            }
            else
            {
                State = PushButtonState.Pressed;
            }
        }
 
        protected override void OnMouseEnter(EventArgs e)
        {
            if (!_showSplit)
            {
                base.OnMouseEnter(e);
                return;
            }
 
            if (State is not (PushButtonState.Pressed or PushButtonState.Disabled))
            {
                State = PushButtonState.Hot;
            }
        }
 
        protected override void OnMouseLeave(EventArgs e)
        {
            if (!_showSplit)
            {
                base.OnMouseLeave(e);
                return;
            }
 
            if (State is not (PushButtonState.Pressed or PushButtonState.Disabled))
            {
                State = Focused ? PushButtonState.Default : PushButtonState.Normal;
            }
        }
 
        protected override void OnMouseUp(MouseEventArgs mevent)
        {
            if (!_showSplit)
            {
                base.OnMouseUp(mevent);
                return;
            }
 
            if (ContextMenuStrip is not { Visible: true })
            {
                SetButtonDrawState();
                if (Parent is not null && Bounds.Contains(Parent.PointToClient(Cursor.Position)) && !_dropDownRectangle.Contains(mevent.Location))
                {
                    OnClick(EventArgs.Empty);
                }
            }
        }
 
        protected override void OnPaint(PaintEventArgs pevent)
        {
            base.OnPaint(pevent);
 
            if (!_showSplit)
            {
                return;
            }
 
            Graphics g = pevent.Graphics;
            Rectangle bounds = new(0, 0, Width, Height);
            TextFormatFlags formatFlags = TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter;
 
            ButtonRenderer.DrawButton(g, bounds, State);
 
            _dropDownRectangle = new Rectangle(bounds.Right - PushButtonWidth - 1, 4, PushButtonWidth, bounds.Height - 8);
 
            if (RightToLeft == RightToLeft.Yes)
            {
                _dropDownRectangle.X = bounds.Left + 1;
 
                g.DrawLine(SystemPens.ButtonHighlight, bounds.Left + PushButtonWidth, 4, bounds.Left + PushButtonWidth, bounds.Bottom - 4);
                g.DrawLine(SystemPens.ButtonHighlight, bounds.Left + PushButtonWidth + 1, 4, bounds.Left + PushButtonWidth + 1, bounds.Bottom - 4);
                bounds.Offset(PushButtonWidth, 0);
                bounds.Width -= PushButtonWidth;
            }
            else
            {
                g.DrawLine(SystemPens.ButtonHighlight, bounds.Right - PushButtonWidth, 4, bounds.Right - PushButtonWidth, bounds.Bottom - 4);
                g.DrawLine(SystemPens.ButtonHighlight, bounds.Right - PushButtonWidth - 1, 4, bounds.Right - PushButtonWidth - 1, bounds.Bottom - 4);
                bounds.Width -= PushButtonWidth;
            }
 
            PaintArrow(g, _dropDownRectangle);
 
            // If we don't use mnemonic, set formatFlag to NoPrefix as this will show ampersand.
            if (!UseMnemonic)
            {
                formatFlags |= TextFormatFlags.NoPrefix;
            }
            else if (!ShowKeyboardCues)
            {
                formatFlags |= TextFormatFlags.HidePrefix;
            }
 
            if (!string.IsNullOrEmpty(Text))
            {
                TextRenderer.DrawText(pevent, Text, Font, bounds, SystemColors.ControlText, formatFlags);
            }
 
            if (Focused)
            {
                bounds.Inflate(-4, -4);
            }
        }
 
        private static void PaintArrow(IDeviceContext deviceContext, Rectangle dropDownRect)
        {
            Point middle = new(
                Convert.ToInt32(dropDownRect.Left + dropDownRect.Width / 2),
                Convert.ToInt32(dropDownRect.Top + dropDownRect.Height / 2));
 
            // If the width is odd - favor pushing it over one pixel right.
            middle.X += (dropDownRect.Width % 2);
 
            Point[] arrow = [
                new(middle.X - s_offset2X, middle.Y - 1),
                new(middle.X + s_offset2X + 1, middle.Y - 1),
                new(middle.X, middle.Y + s_offset2Y)
            ];
 
            deviceContext.TryGetGraphics(create: true)?.FillPolygon(SystemBrushes.ControlText, arrow);
        }
 
        private void ShowContextMenuStrip()
        {
            State = PushButtonState.Pressed;
            if (ContextMenuStrip is not null)
            {
                ContextMenuStrip.Closed += ContextMenuStrip_Closed;
                ContextMenuStrip.Show(this, 0, Height);
            }
        }
 
        private void ContextMenuStrip_Closed(object? sender, ToolStripDropDownClosedEventArgs e)
        {
            if (sender is ContextMenuStrip cms)
            {
                cms.Closed -= ContextMenuStrip_Closed;
            }
 
            SetButtonDrawState();
        }
 
        private void SetButtonDrawState()
        {
            if (Parent is not null && Bounds.Contains(Parent.PointToClient(Cursor.Position)))
            {
                State = PushButtonState.Hot;
            }
            else if (Focused)
            {
                State = PushButtonState.Default;
            }
            else
            {
                State = PushButtonState.Normal;
            }
        }
    }
}