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