带有下拉菜单的Windows.Forms按钮

时间:2012-05-29 16:33:48

标签: c# winforms drop-down-menu

我正在使用.NET上的Windows.Forms开发简单的C#应用​​程序。我需要一些按钮,它会显示一个包含子类别的下拉菜单 - 很像ToolStripMenu,但是按钮,你知道。我搜索它并找不到任何变种。

我的问题是:有没有办法做到这一点,也许有一些秘密按钮属性允许附加菜单?

任何帮助将不胜感激。

10 个答案:

答案 0 :(得分:56)

按钮右下方有箭头,您可以从设计师设置菜单:

ss

使用ShowMenuUnderCursor:

ss

MenuButton类:

public class MenuButton : Button
{
    [DefaultValue(null)]
    public ContextMenuStrip Menu { get; set; }

    [DefaultValue(false)]
    public bool ShowMenuUnderCursor { get; set; }

    protected override void OnMouseDown(MouseEventArgs mevent)
    {
        base.OnMouseDown(mevent);

        if (Menu != null && mevent.Button == MouseButtons.Left)
        {
            Point menuLocation;

            if (ShowMenuUnderCursor)
            {
                menuLocation = mevent.Location;
            }
            else
            {
                menuLocation = new Point(0, Height);
            }

            Menu.Show(this, menuLocation);
        }
    }

    protected override void OnPaint(PaintEventArgs pevent)
    {
        base.OnPaint(pevent);

        if (Menu != null)
        {
            int arrowX = ClientRectangle.Width - 14;
            int arrowY = ClientRectangle.Height / 2 - 1;

            Brush brush = Enabled ? SystemBrushes.ControlText : SystemBrushes.ControlDark;
            Point[] arrows = new Point[] { new Point(arrowX, arrowY), new Point(arrowX + 7, arrowY), new Point(arrowX + 3, arrowY + 4) };
            pevent.Graphics.FillPolygon(brush, arrows);
        }
    }
}

答案 1 :(得分:43)

您可以在点击事件中显示ContextMenuStrip:

private void button1_Click(object sender, EventArgs e) {
  contextMenuStrip1.Show(button1, new Point(0, button1.Height));
}

要自行确定是否在按钮上方或下方显示菜单,您可以尝试使用此代码来测量菜单并确定它是否部分在屏幕外:

private void button1_Click(object sender, EventArgs e) {
  Point screenPoint = button1.PointToScreen(new Point(button1.Left, button1.Bottom));
  if (screenPoint.Y + contextMenuStrip1.Size.Height > Screen.PrimaryScreen.WorkingArea.Height) {
    contextMenuStrip1.Show(button1, new Point(0, -contextMenuStrip1.Size.Height));
  } else {
    contextMenuStrip1.Show(button1, new Point(0, button1.Height));
  }    
}

答案 2 :(得分:15)

稍微扩展@Jaex answer以允许分隔线,如果没有配置任何箭头,则为条件绘制箭头,并为主按钮主体和菜单箭头单独点击事件。

应该注意,为了更好地对齐,您可以设置button.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;

enter image description here

这是我的轻微改进

public class SplitButton : Button
{
    [DefaultValue(null), Browsable(true),
    DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    public ContextMenuStrip Menu { get; set; }

    [DefaultValue(20), Browsable(true),
    DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    public int SplitWidth { get; set; }

    public SplitButton() 
    {
        SplitWidth = 20;
    }

    protected override void OnMouseDown(MouseEventArgs mevent)
    {
        var splitRect = new Rectangle(this.Width - this.SplitWidth, 0, this.SplitWidth, this.Height);

        // Figure out if the button click was on the button itself or the menu split
        if (Menu != null && 
            mevent.Button == MouseButtons.Left &&
            splitRect.Contains(mevent.Location) )
        {
            Menu.Show(this, 0, this.Height);    // Shows menu under button
            //Menu.Show(this, mevent.Location); // Shows menu at click location
        }
        else
        {
            base.OnMouseDown(mevent);
        }
    }

    protected override void OnPaint(PaintEventArgs pevent)
    {
        base.OnPaint(pevent);

        if (this.Menu != null && this.SplitWidth > 0)
        { 
            // Draw the arrow glyph on the right side of the button
            int arrowX = ClientRectangle.Width - 14;
            int arrowY = ClientRectangle.Height / 2 - 1;

            var arrowBrush = Enabled ? SystemBrushes.ControlText : SystemBrushes.ButtonShadow;
            var arrows = new[] { new Point(arrowX, arrowY), new Point(arrowX + 7, arrowY), new Point(arrowX + 3, arrowY + 4) };
            pevent.Graphics.FillPolygon(arrowBrush, arrows);

            // Draw a dashed separator on the left of the arrow
            int lineX = ClientRectangle.Width - this.SplitWidth;
            int lineYFrom = arrowY - 4;
            int lineYTo = arrowY + 8;
            using( var separatorPen = new Pen(Brushes.DarkGray){DashStyle = DashStyle.Dot})
            {
                pevent.Graphics.DrawLine(separatorPen, lineX, lineYFrom, lineX, lineYTo);
            }
        }
    }
}

答案 3 :(得分:6)

很容易,我们可以做到。这可能会有所帮助:)

ContextMenuStrip contextMenuStrip1 = new ContextMenuStrip();

        private void button1_Click(object sender, EventArgs e)
        {
            contextMenuStrip1.Items.Clear();
            contextMenuStrip1.Items.Add("item1");
            contextMenuStrip1.Items.Add("item2");

            contextMenuStrip1.Show(button1, new Point(0, button1.Height));
        }

        private void contextMenuStrip1_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {
            if (e.ClickedItem.Text == "item1")
            {
                MessageBox.Show(e.ClickedItem.Text);
            }
        }

答案 4 :(得分:5)

最简单的选择是在仅展示单个按钮的未停靠的ToolStrip中使用ToolStripDropDownButton。然后你可以添加子项,等等。为此: - 将Toolstrip拖到控件/窗体上 - 使用布局助手添加DropDownButton - 将GripStyle设置为隐藏 - 将Dock设置为None

结果是一个独立的工具栏样式按钮,它支持您描述的下拉行为。

答案 5 :(得分:2)

单击按钮下方的上下文菜单。

答案 6 :(得分:1)

Jaex的MenuButton课程对我来说非常适合。我确实将下面的逻辑添加到OnMouseDown中,这样只有单击箭头才会显示上下文菜单。如果我点击较大的部分,将触发正常点击事件。允许进行“默认”点击操作。

if (Menu != null && mevent.Button == MouseButtons.Left)
{
    if (mevent.Location.X >= this.Width - 14)
    {
        System.Drawing.Point menuLocation;

        if (ShowMenuUnderCursor)
        {
            menuLocation = mevent.Location; 
        }
        else
        {
            menuLocation = new System.Drawing.Point(0, Height);
        }

        Menu.Show(this, menuLocation);
    }
}

认为这可能对某人有用。谢谢Jaex

答案 7 :(得分:0)

Infragistics有WinDropDownButton:http://help.infragistics.com/Help/NetAdvantage/WinForms/2012.1/CLR2.0/html/WinDropDownButton_About_WinDropDownButton.html

所以它确实存在,但你可能不会寻找付费的第三方控制。

答案 8 :(得分:0)

我也在摆弄这个问题,并找到了一个非常简单的解决方案(虽然有点脏兮兮):在ComboBox下放置一个Button,这样它就会显示下一个下拉箭头按钮。

然后使用SelectedIndexChanged的{​​{1}}更改ComboBox行为,或立即执行您想要的操作。

答案 9 :(得分:0)

所以我想出了一个基于工具条的自定义控件,因此可以完全自定义文本和图像,每个按钮/动作都有自己的点击事件。并且可以在winform编辑器中进行设计。有一些小的布局问题,例如下拉项的对齐方式,但并不严重。 该按钮将使单击一次的放置项成为主要项,这可以在 OnActions_DropDownItemClicked() 方法

中更改

Button closed

Button dropped

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design.Serialization;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

//System.Windows.Forms.Design.ControlDesigner
namespace YourNamespace
{
    /// <summary>
    /// Implements a drop button using only standard winform controls
    /// </summary>
    [DesignerSerializer("System.Windows.Forms.Design.ToolStripCodeDomSerializer, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.ComponentModel.Design.Serialization.CodeDomSerializer, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
    [Designer("System.Windows.Forms.Design.ControlDesigner, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
    public class DropButton : ToolStrip
    {
        #region Private Fields

        private List<ActionButtonInfo> _actionButtons = new List<ActionButtonInfo>();

        private ToolStripLayoutStyle _layoutStyle = ToolStripLayoutStyle.Flow;
        private int _splitButtonWidth = 30;
        private System.Windows.Forms.ToolStripDropDownButton btnActions;

        private System.Windows.Forms.ToolStripButton btnMainAction;

        #endregion Private Fields

        #region Public Properties

        /// <summary>
        /// Gets or sets the action buttons.
        /// </summary>
        public List<ActionButtonInfo> ActionButtons
        {
            get
            {
                return this._actionButtons;
            }

            set
            {
                this._actionButtons = value;
                SetupActionButtons();
            }
        }

        /// <summary>
        /// Gets or sets the drop down direction.
        /// </summary>
        public ToolStripDropDownDirection DropDownDirection
        {
            get; set;
        }

        /// <inheritdoc/>
        [Browsable(false)]
        public new ToolStripGripStyle GripStyle => ToolStripGripStyle.Hidden;

        /// <inheritdoc/>
        [Browsable(false)]
        public new ToolStripItemCollection Items
        {
            get
            {
                return base.Items;
            }
        }

        /// <inheritdoc/>
        [Browsable(false)]
        public new ToolStripLayoutStyle LayoutStyle => _layoutStyle;

        public new ToolStripLayoutStyle LayoutStyle1 => ToolStripLayoutStyle.Flow;

        /// <summary>
        /// Gets or sets the split button width.
        /// </summary>
        public int SplitButtonWidth
        {
            get
            {
                return _splitButtonWidth;
            }

            set
            {
                if(value < 10 || value > this.Width)
                {
                    throw new ArgumentOutOfRangeException();
                }

                _splitButtonWidth = value;
                ResizeButtons();
            }
        }

        #endregion Public Properties

        #region Private Methods

        /// <summary>
        /// The actual implementation that adds a button to the button list
        /// </summary>
        /// <param name="abi">The abi.</param>
        private void AddActionButtonImpl(ActionButtonInfo abi)
        {
            ToolStripItem tsi = new ToolStripButton
            {
                AutoSize = false,
                Text = abi.Text,
                Image = abi.Image,
                Tag = abi,
                Height = btnMainAction.Height,
                Width = btnMainAction.Width + btnActions.Width,
                TextImageRelation = TextImageRelation.ImageBeforeText,
                TextAlign = ContentAlignment.MiddleLeft,
                Padding = new System.Windows.Forms.Padding(2, 2, 2, 2)
            };

            btnActions.DropDownItems.Add(tsi);
        }

        private void OnActions_DropDownItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {
            if(e.ClickedItem != null && !String.IsNullOrEmpty(e.ClickedItem.Text))
            {
                ActionButtonInfo abi = e.ClickedItem.Tag as ActionButtonInfo;
                if(abi != null)
                {
                    SetMainButton(abi);
                    abi.Clicked?.Invoke(this, null);
                }
            }
        }

        private void OnbtnActions_DropDownOpening(object sender, EventArgs e)
        {
            ToolStripDropDownMenu tdd = btnActions.DropDown as ToolStripDropDownMenu;

            tdd.DefaultDropDownDirection = ToolStripDropDownDirection.BelowLeft;
            tdd.ShowCheckMargin = false;
            tdd.ShowImageMargin = false;

            tdd.MinimumSize = btnMainAction.Size;
        }

        /// <summary>
        /// Resizes the buttons.
        /// </summary>
        /// <param name="suspend">If true, suspend.</param>
        private void ResizeButtons(bool suspend = true)
        {
            if(btnActions is null || btnMainAction is null)
                return;

            if(suspend)
                this.SuspendLayout();

            int marginX = (this.Margin.Left + this.Margin.Right);
            int marginY = (this.Margin.Top + this.Margin.Bottom);
            btnMainAction.Width = this.Width - _splitButtonWidth - marginX;
            btnActions.Width = _splitButtonWidth - marginX - 1;

            btnMainAction.Height = this.Height - marginY;
            btnActions.Height = this.Height - marginY;

            if(suspend)
                this.ResumeLayout(true);
        }

        /// <summary>
        /// Sets the main button.
        /// </summary>
        /// <param name="abi">The abi.</param>
        private void SetMainButton(ActionButtonInfo abi)
        {
            btnMainAction.Image = abi.Image;
            btnMainAction.Text = abi.Text;

            // btnMainAction.Click += abi.Clicked;
            btnMainAction.Tag = abi;
        }

        /// <summary>
        /// Setups the action buttons.
        /// </summary>
        private void SetupActionButtons()
        {
            if(_actionButtons.Count == 0)
            {
                btnActions.Enabled = false;
                return;
            }

            btnActions.Enabled = true;

            SetMainButton(_actionButtons[0]);

            foreach(ActionButtonInfo abi in _actionButtons)
            {
                AddActionButtonImpl(abi);
            }

            btnActions.DropDownOpening += OnbtnActions_DropDownOpening;
        }

        #endregion Private Methods

        #region Protected Methods

        /// <inheritdoc/>
        protected override void OnCreateControl()
        {
            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(DropButton));

            base.OnCreateControl();

            this.btnMainAction = new System.Windows.Forms.ToolStripButton();
            this.btnActions = new System.Windows.Forms.ToolStripDropDownButton();

            this.SuspendLayout();

            this.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
            this.btnMainAction,
            this.btnActions});

            this.MinimumSize = new Size(100, 40);
            base.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden;
            base.LayoutStyle = System.Windows.Forms.ToolStripLayoutStyle.Flow;
            this.AutoSize = false;
            this.Dock = DockStyle.None;

            // this.ItemClicked += new System.Windows.Forms.ToolStripItemClickedEventHandler(this.toolStripAction_ItemClicked);

            //
            // btnMainAction
            //
            this.btnMainAction.AutoSize = false;
            this.btnMainAction.BackColor = System.Drawing.Color.Gainsboro;
            this.btnMainAction.ForeColor = System.Drawing.Color.Black;
            this.btnMainAction.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
            this.btnMainAction.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None;
            this.btnMainAction.ImageTransparentColor = System.Drawing.Color.Magenta;
            this.btnMainAction.Name = "btnMainAction";
            this.btnMainAction.Size = new System.Drawing.Size(this.Width, this.Height);
            this.btnMainAction.Text = "Test";

            //
            // btnActions
            //
            this.btnActions.AutoSize = false;
            this.btnActions.AutoToolTip = false;
            this.btnActions.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
            this.btnActions.BackColor = System.Drawing.Color.Gainsboro;
            this.btnActions.ForeColor = System.Drawing.Color.Black;
            this.btnActions.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
            this.btnActions.Image = Properties.Resources.DropButtonArrow;
            this.btnActions.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None;
            this.btnActions.Name = "btnActions";
            this.btnActions.ShowDropDownArrow = false;
            this.btnActions.Size = new System.Drawing.Size(_splitButtonWidth, this.Height);
            this.btnActions.TextImageRelation = System.Windows.Forms.TextImageRelation.Overlay;

            btnActions.DropDownDirection = ToolStripDropDownDirection.BelowLeft;
            btnActions.DropDownItemClicked += OnActions_DropDownItemClicked;

            ResizeButtons(false);

            this.ResumeLayout(false);
            this.PerformLayout();
        }

        /// <summary>
        /// Propagate font changes to the child controls
        /// </summary>
        /// <param name="e"></param>
        protected override void OnFontChanged(EventArgs e)
        {
            base.OnFontChanged(e);

            if(btnActions is null || btnMainAction is null)
                return;

            btnMainAction.Font = this.Font;
            btnActions.Font = this.Font;
        }

        /// <inheritdoc/>
        protected override void OnLayout(LayoutEventArgs e)
        {
            ResizeButtons(false);
            base.OnLayout(e);
        }
    
        #endregion Protected Methods

        #region Public Methods

        /// <summary>
        /// Adds an action button.
        /// </summary>
        /// <param name="actionButtonInfo">The action button info.</param>
        public void AddActionButton(ActionButtonInfo actionButtonInfo)
        {
            _actionButtons.Add(actionButtonInfo);

            if(_actionButtons.Count == 1)
                SetupActionButtons();
            else
                AddActionButtonImpl(actionButtonInfo);
        }

        #endregion Public Methods
    }
}