Кнопка Windows.Forms с выпадающим меню
Я занимаюсь разработкой простого приложения на C# с использованием Windows.Forms в.NET. Мне нужна кнопка, которая покажет раскрывающееся меню с подкатегориями - очень похоже на ToolStripMenu, но кнопка, вы знаете. Я искал это и не мог найти никаких вариантов.
Мой вопрос: есть ли способ сделать это, может быть, какое-то свойство секретной кнопки, которое позволяет прикрепить к нему меню?
Любая помощь будет оценена.
10 ответов
Вы можете показать ContextMenuStrip для события click:
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));
}
}
Кнопка имеет стрелку вниз справа от нее, и вы можете выбрать меню из конструктора:
С ShowMenuUnderCursor:
Класс 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.ButtonShadow;
Point[] arrows = new Point[] { new Point(arrowX, arrowY), new Point(arrowX + 7, arrowY), new Point(arrowX + 3, arrowY + 4) };
pevent.Graphics.FillPolygon(brush, arrows);
}
}
}
Раскрывая @Jaex, ответьте немного, чтобы учесть разделительную линию, условное рисование стрелки, если ничего не настроено, и отдельное событие щелчка для основного тела кнопки и стрелки меню.
Следует отметить, что для лучшего выравнивания вы можете установить 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);
}
}
}
}
Простейшим вариантом будет использование ToolStripDropDownButton в открепленной ToolStrip, которая показывает только одну кнопку. Затем вы можете добавить к нему вложенные элементы и т. Д. Чтобы сделать это: - перетащите панель инструментов на элемент управления / форму - используйте помощник по макету, чтобы добавить DropDownButton - установите для GripStyle значение Hidden - установите для Dock значение None
В результате получается отдельная кнопка в стиле панели инструментов, которая поддерживает описанное вами поведение раскрывающегося списка.
Легко было, мы можем это сделать. это может помочь:)
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);
}
}
Класс 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
Инфраструктура имеет WinDropDownButton: http://help.infragistics.com/Help/NetAdvantage/WinForms/2012.1/CLR2.0/html/WinDropDownButton_About_WinDropDownButton.html
Так что, безусловно, существует, однако вы можете не искать платного стороннего контроля.
Поэтому я придумал настраиваемый элемент управления, основанный на панели инструментов и, таким образом, полностью настраиваемый с помощью текста и изображений, имеющий собственное событие щелчка для каждой кнопки/действия. И его можно спроектировать в редакторе 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
}
}
Я тоже возился с этой проблемой и нашел чрезвычайно простое решение (хотя и немного грязное): ComboBox
под Button
, так что он показывает стрелку раскрывающегося списка прямо рядом с кнопкой.
Тогда используйте SelectedIndexChanged
из ComboBox
изменить Button
поведение, или делать то, что вы хотите, чтобы сделать это немедленно