【问题标题】:Windows.Forms button with drop-down menu带有下拉菜单的 Windows.Forms 按钮
【发布时间】:2018-07-27 18:06:29
【问题描述】:

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

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

我们将不胜感激。

【问题讨论】:

    标签: c# winforms drop-down-menu


    【解决方案1】:

    按钮右侧有向下箭头,您可以从设计师那里设置菜单:

    使用 ShowMenuUnderCursor:

    菜单按钮类:

    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);
            }
        }
    }
    

    【讨论】:

    • 感谢您的回复!发布问题时我真的想到了带箭头的按钮,使用 Graphics API 绘制箭头是个好主意。
    • 为什么Menu的值总是null,不管ContextMenuStrip属性值怎么设置?
    • @Alex 将新的 ContextMenuStrip 对象添加到 Menu 属性,而不是 ContextMenuStrip 属性。 myButton.Menu = New ContextMenuStrip .. 你可能在做 myButton.ContextMenuStrip = New ContextMenuStrip
    • 如果您碰巧只想使用按钮的OnPaint 事件而不是从Button 继承,请记住使用按钮的尺寸而不是ClientRectangle 的尺寸。像这样:Button btnSender = (Button)sender; int arrowX = btnSender.Width - 14; int arrowY = btnSender.Height / 2 - 1;
    • 喜欢这个!但是有没有人有解决方案,将缩放的 BackgroundImage 向左移动,这样箭头就不会覆盖它?
    【解决方案2】:

    您可以在点击事件上显示 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));
      }    
    }
    

    【讨论】:

    • 看起来像我需要的。您知道确定菜单是从按钮底部下拉还是从按钮顶部边框下拉的好方法?
    • @tonytony 它应该会自动发生。如果菜单位于屏幕底部下方,windows 会将其向上推以使其全部可见。
    • 重叠按钮怎么样?我想我应该知道它会上涨还是下跌。
    【解决方案3】:

    稍微扩展@Jaex answer 以允许分隔线、箭头的条件绘制(如果未配置任何内容)以及主按钮主体和菜单箭头的单独单击事件。

    需要注意的是,为了更好的对齐可以设置button.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;

    这是我的小改进

    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);
                }
            }
        }
    }
    

    【讨论】:

    • var splitRect = new Rectangle(this.Width - this.SplitWidth, this.Location.Y, this.SplitWidth, this.Height);第二个参数要改成0
    • 杰基是对的。如果该参数不为 0,则下拉“点击区域”不会覆盖按钮的所有高度。
    • 我已经编辑了代码以修复 @Jacky 发现的错误。
    • 如何不使用鼠标,只使用键盘来调用下拉菜单?
    • 很棒的代码。我建议将第 7 行从末尾更新为 using (var separatorPen = new Pen(Brushes.DarkGray) { DashStyle = System.Drawing.Drawing2D.DashStyle.Dot }) 这样就不需要包含库。
    【解决方案4】:

    最简单的选择是在仅显示单个按钮的未停靠 ToolStrip 中使用 ToolStripDropDownButton。然后您可以向其中添加子项目等。要做到这一点: - 将工具条拖到您的控件/表单上 - 使用布局助手添加 DropDownButton - 将 GripStyle 设置为隐藏 - 将 Dock 设置为无

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

    【讨论】:

    • 感谢您的回复,这很简洁明了,但不是我所需要的。菜单按钮有点没有实体,而我想要更肉的东西:)
    • 实际上要使其真正按预期工作,您添加一个 ToolStripButton 作为主按钮,然后在放置项目所在的位置添加一个 ToolStripDropDownButton。这样您就不必处理按钮单击和下拉处理的奇怪逻辑。并且您可以使用您选择的图像设置下拉箭头的样式。
    【解决方案5】:

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

    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);
                }
            }
    

    【讨论】:

      【解决方案6】:

      上面 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);
          }
      }
      

      认为这可能对某人有用。谢谢贾克斯

      【讨论】:

        【解决方案7】:

        单击时在按钮下方显示上下文菜单。

        【讨论】:

          【解决方案8】:

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

          所以它肯定存在,但是您可能不是在寻找付费的第三方控件。

          【讨论】:

            【解决方案9】:

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

            然后使用ComboBox 中的SelectedIndexChanged 来更改Button 的行为,或者立即执行您希望它执行的操作。

            【讨论】:

            • 有趣的解决方案!不过,我担心标签焦点行为会让你失望。
            • 只需在ComboBox 上将TabStop 设置为False 并抓住Button 上的向下箭头键?
            • 也许吧。老实说,我很久以前就放弃了 windows.foms 和 c#。
            • 尝试使用不同的屏幕布局和设置,会搞砸的。获得一个干净的解决方案并使用自定义按钮或 ToolStripButton。
            【解决方案10】:

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

            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
                }
            }
            

            【讨论】:

              猜你喜欢
              • 2013-07-06
              • 2018-09-16
              • 2022-12-31
              • 2019-07-04
              • 2014-11-20
              • 2011-03-25
              • 1970-01-01
              • 2021-12-16
              • 2010-09-19
              相关资源
              最近更新 更多