2023-09-24 17:26:46 -04:00
|
|
|
|
using System.ComponentModel;
|
2017-11-27 13:11:20 -05:00
|
|
|
|
using System.Drawing;
|
|
|
|
|
using System.Windows.Forms;
|
|
|
|
|
using static Vanara.PInvoke.ComCtl32;
|
2019-08-27 18:03:21 -04:00
|
|
|
|
using static Vanara.PInvoke.User32;
|
2017-11-27 13:11:20 -05:00
|
|
|
|
|
2023-03-31 11:47:53 -04:00
|
|
|
|
namespace Vanara.Windows.Forms;
|
|
|
|
|
|
2023-09-29 13:58:35 -04:00
|
|
|
|
/// <summary>The SplitButton is a composite control with which the user can select from a drop-down list bound to the button.</summary>
|
2023-03-31 11:47:53 -04:00
|
|
|
|
public class SplitButton : VistaButtonBase
|
2017-11-27 13:11:20 -05:00
|
|
|
|
{
|
2023-09-29 13:58:35 -04:00
|
|
|
|
private ContextMenuStrip? contextMenu;
|
2023-03-31 11:47:53 -04:00
|
|
|
|
private bool showingDropdown;
|
2023-09-29 13:58:35 -04:00
|
|
|
|
private ImageList? splitButtonImageList;
|
2023-03-31 11:47:53 -04:00
|
|
|
|
private SplitButtonInfoStyle style = 0;
|
|
|
|
|
|
2023-09-29 13:58:35 -04:00
|
|
|
|
/// <summary>Occurs when the split label is clicked.</summary>
|
2023-03-31 11:47:53 -04:00
|
|
|
|
[Description("Occurs when the split button is clicked."), Category("Action")]
|
2023-09-29 13:58:35 -04:00
|
|
|
|
public event EventHandler<SplitMenuEventArgs>? SplitClick;
|
2023-03-31 11:47:53 -04:00
|
|
|
|
|
2023-09-29 13:58:35 -04:00
|
|
|
|
/// <summary>Occurs when the split label is clicked, but before the associated context menu is displayed by the control.</summary>
|
2023-03-31 11:47:53 -04:00
|
|
|
|
[Description("Occurs when the split label is clicked, but before the associated context menu is displayed."), Category("Action")]
|
2023-09-29 13:58:35 -04:00
|
|
|
|
public event EventHandler<SplitMenuEventArgs>? SplitMenuOpening;
|
|
|
|
|
|
|
|
|
|
/// <summary>Gets or sets the visibility of the split button divider.</summary>
|
|
|
|
|
/// <value><c>true</c> if hiding split separator; otherwise, <c>false</c>.</value>
|
2023-03-31 11:47:53 -04:00
|
|
|
|
[Description("Sets the visibility of the split button divider."), Category("Appearance"), DefaultValue(false)]
|
|
|
|
|
public bool HideSplitSeparator
|
2017-11-27 13:11:20 -05:00
|
|
|
|
{
|
2023-09-29 13:58:35 -04:00
|
|
|
|
get => GetSplitStyle(SplitButtonInfoStyle.BCSS_NOSPLIT);
|
|
|
|
|
set => SetSplitStyle(SplitButtonInfoStyle.BCSS_NOSPLIT, value);
|
2023-03-31 11:47:53 -04:00
|
|
|
|
}
|
2017-11-27 13:11:20 -05:00
|
|
|
|
|
2023-09-29 13:58:35 -04:00
|
|
|
|
/// <summary>Gets or sets the alignment of the split button image.</summary>
|
|
|
|
|
/// <value>The split button alignment.</value>
|
2023-03-31 11:47:53 -04:00
|
|
|
|
[Description("Sets the alignment of the split button image."), Category("Appearance"), DefaultValue(typeof(LeftRightAlignment), "Right")]
|
|
|
|
|
public LeftRightAlignment SplitButtonAlignment
|
|
|
|
|
{
|
2023-09-29 13:58:35 -04:00
|
|
|
|
get => GetSplitStyle(SplitButtonInfoStyle.BCSS_ALIGNLEFT) ? LeftRightAlignment.Left : LeftRightAlignment.Right;
|
|
|
|
|
set => SetSplitStyle(SplitButtonInfoStyle.BCSS_ALIGNLEFT, value == LeftRightAlignment.Left);
|
2023-03-31 11:47:53 -04:00
|
|
|
|
}
|
2017-11-27 13:11:20 -05:00
|
|
|
|
|
2023-09-29 13:58:35 -04:00
|
|
|
|
/// <summary>Gets or sets the split button image list.</summary>
|
|
|
|
|
/// <value>The split button image list.</value>
|
2023-03-31 11:47:53 -04:00
|
|
|
|
[Description("ImageList containing an image to show on split button."), Category("Appearance"), DefaultValue(null)]
|
2023-09-29 13:58:35 -04:00
|
|
|
|
public ImageList? SplitButtonImageList
|
2023-03-31 11:47:53 -04:00
|
|
|
|
{
|
2023-09-29 13:58:35 -04:00
|
|
|
|
get => splitButtonImageList;
|
|
|
|
|
set
|
2017-11-27 13:11:20 -05:00
|
|
|
|
{
|
2023-03-31 11:47:53 -04:00
|
|
|
|
if (splitButtonImageList != value)
|
2017-11-27 13:11:20 -05:00
|
|
|
|
{
|
2023-03-31 11:47:53 -04:00
|
|
|
|
splitButtonImageList = value;
|
|
|
|
|
if (IsHandleCreated)
|
2017-11-27 13:11:20 -05:00
|
|
|
|
{
|
2023-03-31 11:47:53 -04:00
|
|
|
|
if (splitButtonImageList != null)
|
|
|
|
|
SetSplitInfo(new BUTTON_SPLITINFO(splitButtonImageList.Handle));
|
|
|
|
|
else
|
|
|
|
|
RecreateHandle();
|
2017-11-27 13:11:20 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-03-31 11:47:53 -04:00
|
|
|
|
}
|
2017-11-27 13:11:20 -05:00
|
|
|
|
|
2023-09-29 13:58:35 -04:00
|
|
|
|
/// <summary>Gets or sets the associated context menu that is displayed when the split glyph of the button is clicked.</summary>
|
2023-03-31 11:47:53 -04:00
|
|
|
|
[Description("Sets the context menu that is displayed by clicking on the split button."), Category("Behavior"), DefaultValue(null)]
|
2023-09-29 13:58:35 -04:00
|
|
|
|
public ContextMenuStrip? SplitMenuStrip
|
2023-03-31 11:47:53 -04:00
|
|
|
|
{
|
2023-09-29 13:58:35 -04:00
|
|
|
|
get => contextMenu;
|
|
|
|
|
set
|
2017-11-27 13:11:20 -05:00
|
|
|
|
{
|
2023-03-31 11:47:53 -04:00
|
|
|
|
if (value != contextMenu)
|
2017-11-27 13:11:20 -05:00
|
|
|
|
{
|
2023-03-31 11:47:53 -04:00
|
|
|
|
if (contextMenu != null)
|
|
|
|
|
contextMenu.Closed -= contextMenu_Closed;
|
|
|
|
|
contextMenu = value;
|
2023-09-29 13:58:35 -04:00
|
|
|
|
if (contextMenu != null)
|
|
|
|
|
contextMenu.Closed += contextMenu_Closed;
|
2017-11-27 13:11:20 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-03-31 11:47:53 -04:00
|
|
|
|
}
|
2017-11-27 13:11:20 -05:00
|
|
|
|
|
2023-09-29 13:58:35 -04:00
|
|
|
|
/// <summary>Gets or sets a value indicating whether to stretch the split button image.</summary>
|
|
|
|
|
/// <value><c>true</c> to stretch split button image; otherwise, <c>false</c>.</value>
|
2023-03-31 11:47:53 -04:00
|
|
|
|
[Description("Stretch glyph, but try to retain aspect ratio."), Category("Appearance"), DefaultValue(false)]
|
|
|
|
|
public bool StretchSplitButtonImage
|
|
|
|
|
{
|
|
|
|
|
get => GetSplitStyle(SplitButtonInfoStyle.BCSS_STRETCH); set => SetSplitStyle(SplitButtonInfoStyle.BCSS_STRETCH, value);
|
|
|
|
|
}
|
2017-11-27 13:11:20 -05:00
|
|
|
|
|
2023-09-29 13:58:35 -04:00
|
|
|
|
/// <summary>Gets a <see cref="T:System.Windows.Forms.CreateParams"/> on the base class when creating a window.</summary>
|
2023-03-31 11:47:53 -04:00
|
|
|
|
protected override CreateParams CreateParams
|
|
|
|
|
{
|
|
|
|
|
get
|
2017-11-27 13:11:20 -05:00
|
|
|
|
{
|
2023-09-29 13:58:35 -04:00
|
|
|
|
CreateParams cp = base.CreateParams;
|
2023-03-31 11:47:53 -04:00
|
|
|
|
if (IsPlatformSupported)
|
|
|
|
|
cp.Style |= (int)ButtonStyle.BS_SPLITBUTTON;
|
|
|
|
|
return cp;
|
2017-11-27 13:11:20 -05:00
|
|
|
|
}
|
2023-03-31 11:47:53 -04:00
|
|
|
|
}
|
2017-11-27 13:11:20 -05:00
|
|
|
|
|
2023-09-29 13:58:35 -04:00
|
|
|
|
/// <summary>Raises the <see cref="E:System.Windows.Forms.Control.HandleCreated"/> event.</summary>
|
|
|
|
|
/// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.</param>
|
2023-03-31 11:47:53 -04:00
|
|
|
|
protected override void OnHandleCreated(EventArgs e)
|
|
|
|
|
{
|
|
|
|
|
base.OnHandleCreated(e);
|
|
|
|
|
if (style != 0)
|
|
|
|
|
SetSplitInfo(new BUTTON_SPLITINFO(style));
|
|
|
|
|
if (splitButtonImageList != null)
|
|
|
|
|
SetSplitInfo(new BUTTON_SPLITINFO(splitButtonImageList.Handle));
|
|
|
|
|
}
|
2017-11-27 13:11:20 -05:00
|
|
|
|
|
2023-09-29 13:58:35 -04:00
|
|
|
|
/// <summary>Raises the <see cref="E:SplitClick"/> event.</summary>
|
2023-03-31 11:47:53 -04:00
|
|
|
|
/// <param name="e">The <see cref="SplitMenuEventArgs"/> instance containing the event data.</param>
|
|
|
|
|
protected virtual void OnSplitClick(SplitMenuEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
if (SplitMenuStrip != null)
|
2017-11-27 13:11:20 -05:00
|
|
|
|
{
|
2023-03-31 11:47:53 -04:00
|
|
|
|
if (showingDropdown)
|
2017-11-27 13:11:20 -05:00
|
|
|
|
{
|
2023-03-31 11:47:53 -04:00
|
|
|
|
//SplitMenuStrip.Close();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
SplitMenuOpening?.Invoke(this, e);
|
|
|
|
|
|
|
|
|
|
if (!e.PreventOpening)
|
2017-11-27 13:11:20 -05:00
|
|
|
|
{
|
2023-03-31 11:47:53 -04:00
|
|
|
|
SplitMenuStrip.Width = e.DrawArea.Width;
|
|
|
|
|
showingDropdown = true;
|
|
|
|
|
SplitMenuStrip.Show(this, new Point(e.DrawArea.Left, e.DrawArea.Bottom));
|
2017-11-27 13:11:20 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-31 11:47:53 -04:00
|
|
|
|
SplitClick?.Invoke(this, e);
|
|
|
|
|
}
|
2017-11-27 13:11:20 -05:00
|
|
|
|
|
2023-09-29 13:58:35 -04:00
|
|
|
|
/// <summary>Processes Windows messages.</summary>
|
|
|
|
|
/// <param name="m">The Windows <see cref="T:System.Windows.Forms.Message"/> to process.</param>
|
2023-03-31 11:47:53 -04:00
|
|
|
|
protected override void WndProc(ref Message m)
|
|
|
|
|
{
|
|
|
|
|
if (m.Msg == OCM_NOTIFY)
|
2017-11-27 13:11:20 -05:00
|
|
|
|
{
|
2023-09-29 13:58:35 -04:00
|
|
|
|
NMHDR nmhdr = m.LParam.ToStructure<NMHDR>();
|
2023-03-31 11:47:53 -04:00
|
|
|
|
if ((ButtonNotification)nmhdr.code == ButtonNotification.BCN_DROPDOWN && SplitMenuStrip != null)
|
|
|
|
|
OnSplitClick(new SplitMenuEventArgs(ClientRectangle));
|
2017-11-27 13:11:20 -05:00
|
|
|
|
}
|
2023-03-31 11:47:53 -04:00
|
|
|
|
base.WndProc(ref m);
|
|
|
|
|
}
|
2017-11-27 13:11:20 -05:00
|
|
|
|
|
2023-09-29 13:58:35 -04:00
|
|
|
|
private void contextMenu_Closed(object? sender, ToolStripDropDownClosedEventArgs e) => showingDropdown = false;
|
2017-11-27 13:11:20 -05:00
|
|
|
|
|
2023-03-31 11:47:53 -04:00
|
|
|
|
private BUTTON_SPLITINFO GetSplitInfo()
|
|
|
|
|
{
|
2023-09-29 13:58:35 -04:00
|
|
|
|
BUTTON_SPLITINFO info = new();
|
2023-03-31 11:47:53 -04:00
|
|
|
|
SendMessage(Handle, ButtonMessage.BCM_GETSPLITINFO, 0, ref info);
|
|
|
|
|
return info;
|
|
|
|
|
}
|
2017-11-27 13:11:20 -05:00
|
|
|
|
|
2023-03-31 11:47:53 -04:00
|
|
|
|
private bool GetSplitStyle(SplitButtonInfoStyle btnStyle) => (style & btnStyle) == btnStyle;
|
2017-11-27 13:11:20 -05:00
|
|
|
|
|
2023-03-31 11:47:53 -04:00
|
|
|
|
private void SetSplitInfo(BUTTON_SPLITINFO info)
|
|
|
|
|
{
|
|
|
|
|
SendMessage(Handle, ButtonMessage.BCM_SETSPLITINFO, 0, ref info);
|
|
|
|
|
Refresh();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void SetSplitStyle(SplitButtonInfoStyle btnStyle, bool value)
|
|
|
|
|
{
|
|
|
|
|
if (value != GetSplitStyle(btnStyle))
|
2017-11-27 13:11:20 -05:00
|
|
|
|
{
|
2023-03-31 11:47:53 -04:00
|
|
|
|
if (value)
|
|
|
|
|
style |= btnStyle;
|
|
|
|
|
else
|
|
|
|
|
style &= ~btnStyle;
|
|
|
|
|
if (IsHandleCreated)
|
|
|
|
|
SetSplitInfo(new BUTTON_SPLITINFO(style));
|
2017-11-27 13:11:20 -05:00
|
|
|
|
}
|
2023-03-31 11:47:53 -04:00
|
|
|
|
}
|
2017-11-27 13:11:20 -05:00
|
|
|
|
|
2023-09-29 13:58:35 -04:00
|
|
|
|
/// <summary>Provides data for the clicking of split buttons and the opening of context menus.</summary>
|
2023-03-31 11:47:53 -04:00
|
|
|
|
public class SplitMenuEventArgs : EventArgs
|
|
|
|
|
{
|
|
|
|
|
internal SplitMenuEventArgs(Rectangle drawArea)
|
2017-11-27 13:11:20 -05:00
|
|
|
|
{
|
2023-03-31 11:47:53 -04:00
|
|
|
|
PreventOpening = false;
|
|
|
|
|
DrawArea = drawArea;
|
|
|
|
|
}
|
2017-11-27 13:11:20 -05:00
|
|
|
|
|
2023-03-31 11:47:53 -04:00
|
|
|
|
/// <summary>Represents the bounding box of the clicked button.</summary>
|
2023-09-29 13:58:35 -04:00
|
|
|
|
/// <remarks>
|
|
|
|
|
/// A menu should be opened, with top-left coordinates in the left-bottom point of the rectangle and with width equal (or greater)
|
|
|
|
|
/// than the width of the rectangle.
|
|
|
|
|
/// </remarks>
|
2023-03-31 11:47:53 -04:00
|
|
|
|
public Rectangle DrawArea { get; set; }
|
2017-11-27 13:11:20 -05:00
|
|
|
|
|
2023-03-31 11:47:53 -04:00
|
|
|
|
/// <summary>Set to true if you want to prevent the menu from opening.</summary>
|
|
|
|
|
public bool PreventOpening { get; set; }
|
2017-11-27 13:11:20 -05:00
|
|
|
|
}
|
|
|
|
|
}
|