using System; using System.ComponentModel; using System.Drawing; using System.Runtime.InteropServices; using System.Windows.Forms; using Vanara.Extensions; using static Vanara.PInvoke.ComCtl32; using static Vanara.PInvoke.User32; namespace Vanara.Windows.Forms { /// /// The SplitButton is a composite control with which the user can select from a drop-down list bound to the button. /// public class SplitButton : VistaButtonBase { //private const int BCM_SETDROPDOWNSTATE = 0x1606; //private const int BCM_SETSPLITINFO = 0x1607; //private const int BCM_GETSPLITINFO = 0x1608; //private const int NM_GETCUSTOMSPLITRECT = -1247; //private const int BCN_DROPDOWN = -1248; //private const int BCN_HOTITEMCHANGE = -1249; //private const int BS_SPLITBUTTON = 0xC; private ContextMenuStrip contextMenu; private bool showingDropdown; private ImageList splitButtonImageList; private SplitButtonInfoStyle style = 0; /// /// Occurs when the split label is clicked. /// [Description("Occurs when the split button is clicked."), Category("Action")] public event EventHandler SplitClick; /// /// Occurs when the split label is clicked, but before the associated context menu is displayed by the control. /// [Description("Occurs when the split label is clicked, but before the associated context menu is displayed."), Category("Action")] public event EventHandler SplitMenuOpening; /// /// Gets or sets the visibility of the split button divider. /// /// /// true if hiding split separator; otherwise, false. /// [Description("Sets the visibility of the split button divider."), Category("Appearance"), DefaultValue(false)] public bool HideSplitSeparator { get => GetSplitStyle(SplitButtonInfoStyle.BCSS_NOSPLIT); set => SetSplitStyle(SplitButtonInfoStyle.BCSS_NOSPLIT, value); } /// /// Gets or sets the alignment of the split button image. /// /// /// The split button alignment. /// [Description("Sets the alignment of the split button image."), Category("Appearance"), DefaultValue(typeof(LeftRightAlignment), "Right")] public LeftRightAlignment SplitButtonAlignment { get => GetSplitStyle(SplitButtonInfoStyle.BCSS_ALIGNLEFT) ? LeftRightAlignment.Left : LeftRightAlignment.Right; set => SetSplitStyle(SplitButtonInfoStyle.BCSS_ALIGNLEFT, value == LeftRightAlignment.Left); } /// /// Gets or sets the split button image list. /// /// /// The split button image list. /// [Description("ImageList containing an image to show on split button."), Category("Appearance"), DefaultValue(null)] public ImageList SplitButtonImageList { get => splitButtonImageList; set { if (splitButtonImageList != value) { splitButtonImageList = value; if (IsHandleCreated) { if (splitButtonImageList != null) SetSplitInfo(new BUTTON_SPLITINFO(splitButtonImageList.Handle)); else RecreateHandle(); } } } } /// /// Gets or sets the associated context menu that is displayed when the split glyph of the button is clicked. /// [Description("Sets the context menu that is displayed by clicking on the split button."), Category("Behavior"), DefaultValue(null)] public ContextMenuStrip SplitMenuStrip { get => contextMenu; set { if (value != contextMenu) { if (contextMenu != null) contextMenu.Closed -= contextMenu_Closed; contextMenu = value; contextMenu.Closed += contextMenu_Closed; } } } /// /// Gets or sets a value indicating whether to stretch the split button image. /// /// /// true to stretch split button image; otherwise, false. /// [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); } /// /// Gets a on the base class when creating a window. /// protected override CreateParams CreateParams { get { var cp = base.CreateParams; if (IsPlatformSupported) cp.Style |= (int)ButtonStyle.BS_SPLITBUTTON; return cp; } } /// /// Raises the event. /// /// An that contains the event data. 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)); } /// /// Raises the event. /// /// The instance containing the event data. protected virtual void OnSplitClick(SplitMenuEventArgs e) { if (SplitMenuStrip != null) { if (showingDropdown) { //SplitMenuStrip.Close(); } else { SplitMenuOpening?.Invoke(this, e); if (!e.PreventOpening) { SplitMenuStrip.Width = e.DrawArea.Width; showingDropdown = true; SplitMenuStrip.Show(this, new Point(e.DrawArea.Left, e.DrawArea.Bottom)); } } } SplitClick?.Invoke(this, e); } /// /// Processes Windows messages. /// /// The Windows to process. protected override void WndProc(ref Message m) { if (m.Msg == OCM_NOTIFY) { var nmhdr = m.LParam.ToStructure(); if ((ButtonNotification)nmhdr.code == ButtonNotification.BCN_DROPDOWN && SplitMenuStrip != null) OnSplitClick(new SplitMenuEventArgs(ClientRectangle)); } base.WndProc(ref m); } private void contextMenu_Closed(object sender, ToolStripDropDownClosedEventArgs e) { showingDropdown = false; } private BUTTON_SPLITINFO GetSplitInfo() { var info = new BUTTON_SPLITINFO(); SendMessage(Handle, ButtonMessage.BCM_GETSPLITINFO, 0, ref info); return info; } private bool GetSplitStyle(SplitButtonInfoStyle btnStyle) => (style & btnStyle) == btnStyle; 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)) { if (value) style |= btnStyle; else style &= ~btnStyle; if (IsHandleCreated) SetSplitInfo(new BUTTON_SPLITINFO(style)); } } /// /// Provides data for the clicking of split buttons and the opening of context menus. /// public class SplitMenuEventArgs : EventArgs { internal SplitMenuEventArgs(Rectangle drawArea) { PreventOpening = false; DrawArea = drawArea; } /// Represents the bounding box of the clicked button. /// 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. public Rectangle DrawArea { get; set; } /// Set to true if you want to prevent the menu from opening. public bool PreventOpening { get; set; } } } }