我可以在 WinForms 中的 ToolStripSplitButton 上设置 Checked 状态吗?
Can I have a Checked state on a ToolStripSplitButton in WinForms?
有没有办法在 ToolStripSplitButton 上设置 Checked 状态?我想要一个带有 Checked 属性 的 ToolStripButton 来确定覆盖是否显示在我的应用程序 window 中。我的用户希望通过直接交互来控制叠加层的不透明度,为此我想象有一个拆分按钮,其中辅助按钮打开一个滑块来控制不透明度。 (这当然不是标准功能,但我有信心能够将它们组合在一起)。
但是,ToolStripSplitButton 没有 Checked 属性,所以我不能直接这样做。我试图让一个标准的 ToolStripButton 旁边有一个单独的 ToolStripSplitButton,DropDownButtonWidth = 11,Margin = -5, 1, 0, 2,这样它看起来就像属于 ToolStripButton。它看起来不错,但为了完成它们是单个按钮的错觉,我需要让它们都使用相同的 VisualStyles.PushButtonState 进行绘制 - 所以当鼠标悬停在它们中的任何一个上时它们都会变热。
首先,我尝试在两个按钮上使用 MouseEnter 和 MouseLeave 事件来尝试更改 other 按钮的背景颜色,但没有效果。然后我尝试使用每个按钮的 Paint 事件来尝试使用带有 PushButtonState = Hot 的 ButtonRenderer.DrawButton 来渲染它们,但这似乎也不起作用并且变得非常混乱。看来一定有更好的办法!
是否有简单的或至少是实用的解决方案?
编辑:我想要的效果是这样的:
我建议您创建自己的 UserControl
并将其用作 ToolStripItem
。
toolStripMenuItem.DropDownItems.Add(new ToolStripControlHost(new OverlayControl()));
这里是OverlayControl
的一个简单例子,它是根据你的描述推导出来的(我建议你用Designer
创建一个真正的UserControl
)。
public class OverlayControl : UserControl
{
private readonly CheckBox _checkBox = new CheckBox();
private readonly TrackBar _trackBar = new TrackBar();
public ToolStripExtended()
{
_checkBox.Text = "Overlay";
BackColor = SystemColors.Window;
_checkBox.AutoSize = true;
_trackBar.AutoSize = true;
var flowLayoutPanel = new FlowLayoutPanel {AutoSize = true, AutoSizeMode = AutoSizeMode.GrowAndShrink};
flowLayoutPanel.Controls.AddRange( new Control[] { _checkBox, _trackBar });
Controls.Add(flowLayoutPanel);
AutoSize = true;
AutoSizeMode = AutoSizeMode.GrowAndShrink;
PerformLayout();
}
}
我想到的解决方案是通过继承 ToolStripSplitButton 创建我自己的按钮,添加我自己的 Checked 属性,然后在选中时手动绘制:
/// <summary>
/// ToolStripSplitCheckButton adds a Check property to a ToolStripSplitButton.
/// </summary>
public partial class ToolStripSplitCheckButton : ToolStripSplitButton
{
//==============================================================================
// Inner class: ToolBarButonSplitCheckButtonEventArgs
//==============================================================================
/// <summary>
/// The event args for the check button click event. To be able to use the OnCheckedChanged
/// event, we must also record a dummy button as well as this one (hack).
/// </summary>
public class ToolBarButonSplitCheckButtonEventArgs : ToolBarButtonClickEventArgs
{
/// <summary>
/// Constructor.
/// </summary>
/// <param name="split_button">The sender split check button</param>
public ToolBarButonSplitCheckButtonEventArgs(ToolStripSplitCheckButton split_button)
: base(new ToolBarButton("Dummy Button")) // Hack - Dummy Button is not used
{
SplitCheckButton = split_button;
}
/// <summary>
/// The ToolStripSplitCheckButton to be sent as an argument.
/// </summary>
public ToolStripSplitCheckButton SplitCheckButton { get; set; }
}
//==========================================================================
// Construction
public ToolStripSplitCheckButton()
{
m_checked = false;
m_mouse_over = false;
}
//==========================================================================
// Properties
/// <summary>
/// Indicates whether the button should toggle its Checked state on click.
/// </summary>
[Category("Behavior"),
Description("Indicates whether the item should toggle its selected state when clicked."),
DefaultValue(true)]
public bool CheckOnClick { get; set; }
/// <summary>
/// Indictates the Checked state of the button.
/// </summary>
[Category("Behavior"),
Description("Indicates whether the ToolStripSplitCheckButton is pressed in or not pressed in."),
DefaultValue(false)]
public bool Checked { get { return m_checked; } set { m_checked = value; } }
//==========================================================================
// Methods
/// <summary>
/// Toggle the click state on button click.
/// </summary>
protected override void OnButtonClick(EventArgs e)
{
if (CheckOnClick) {
m_checked = !m_checked;
// Raise the OnCheckStateChanged event when the button is clicked
if (OnCheckChanged != null) {
ToolBarButonSplitCheckButtonEventArgs args = new ToolBarButonSplitCheckButtonEventArgs(this);
OnCheckChanged(this, args);
}
}
base.OnButtonClick(e);
}
/// <summary>
/// On mouse enter, record that we are over the button.
/// </summary>
protected override void OnMouseEnter(EventArgs e)
{
m_mouse_over = true;
base.OnMouseEnter(e);
this.Invalidate();
}
/// <summary>
/// On mouse leave, record that we are no longer over the button.
/// </summary>
protected override void OnMouseLeave(EventArgs e)
{
m_mouse_over = false;
base.OnMouseLeave(e);
this.Invalidate();
}
/// <summary>
/// Paint the check highlight when required.
/// </summary>
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (m_checked) {
// I can't get the check + mouse over to render properly, so just give the button a colour fill - Hack
if (m_mouse_over) {
using (Brush brush = new SolidBrush(Color.FromArgb(64, SystemColors.MenuHighlight))) {
e.Graphics.FillRectangle(brush, ButtonBounds);
}
}
ControlPaint.DrawBorder(
e.Graphics,
e.ClipRectangle, // To draw around the button + drop-down
//this.ButtonBounds, // To draw only around the button
SystemColors.MenuHighlight,
ButtonBorderStyle.Solid);
}
}
//==========================================================================
// Member Variables
// The delegate that acts as a signature for the function that is ultimately called
// when the OnCheckChanged event is triggered.
public delegate void SplitCheckButtonEventHandler(object source, EventArgs e);
public event SplitCheckButtonEventHandler OnCheckChanged;
private bool m_checked;
private bool m_mouse_over;
}
要使用它,请处理此按钮的 OnCheckChanged 事件。
然而,这有一些笨拙的技巧。为了能够为复选按钮引发 OnCheckedChanged 事件,事件参数除了包含实际按钮外,还必须包含对未使用的虚拟按钮的引用。即使在尝试了各种渲染器之后,我也无法让选中的 + 鼠标悬停在渲染上与普通选中按钮的渲染完全相同,所以我只绘制了一个颜色叠加层,这几乎是正确的但不完全正确。
有没有办法在 ToolStripSplitButton 上设置 Checked 状态?我想要一个带有 Checked 属性 的 ToolStripButton 来确定覆盖是否显示在我的应用程序 window 中。我的用户希望通过直接交互来控制叠加层的不透明度,为此我想象有一个拆分按钮,其中辅助按钮打开一个滑块来控制不透明度。 (这当然不是标准功能,但我有信心能够将它们组合在一起)。
但是,ToolStripSplitButton 没有 Checked 属性,所以我不能直接这样做。我试图让一个标准的 ToolStripButton 旁边有一个单独的 ToolStripSplitButton,DropDownButtonWidth = 11,Margin = -5, 1, 0, 2,这样它看起来就像属于 ToolStripButton。它看起来不错,但为了完成它们是单个按钮的错觉,我需要让它们都使用相同的 VisualStyles.PushButtonState 进行绘制 - 所以当鼠标悬停在它们中的任何一个上时它们都会变热。
首先,我尝试在两个按钮上使用 MouseEnter 和 MouseLeave 事件来尝试更改 other 按钮的背景颜色,但没有效果。然后我尝试使用每个按钮的 Paint 事件来尝试使用带有 PushButtonState = Hot 的 ButtonRenderer.DrawButton 来渲染它们,但这似乎也不起作用并且变得非常混乱。看来一定有更好的办法!
是否有简单的或至少是实用的解决方案?
编辑:我想要的效果是这样的:
我建议您创建自己的 UserControl
并将其用作 ToolStripItem
。
toolStripMenuItem.DropDownItems.Add(new ToolStripControlHost(new OverlayControl()));
这里是OverlayControl
的一个简单例子,它是根据你的描述推导出来的(我建议你用Designer
创建一个真正的UserControl
)。
public class OverlayControl : UserControl
{
private readonly CheckBox _checkBox = new CheckBox();
private readonly TrackBar _trackBar = new TrackBar();
public ToolStripExtended()
{
_checkBox.Text = "Overlay";
BackColor = SystemColors.Window;
_checkBox.AutoSize = true;
_trackBar.AutoSize = true;
var flowLayoutPanel = new FlowLayoutPanel {AutoSize = true, AutoSizeMode = AutoSizeMode.GrowAndShrink};
flowLayoutPanel.Controls.AddRange( new Control[] { _checkBox, _trackBar });
Controls.Add(flowLayoutPanel);
AutoSize = true;
AutoSizeMode = AutoSizeMode.GrowAndShrink;
PerformLayout();
}
}
我想到的解决方案是通过继承 ToolStripSplitButton 创建我自己的按钮,添加我自己的 Checked 属性,然后在选中时手动绘制:
/// <summary>
/// ToolStripSplitCheckButton adds a Check property to a ToolStripSplitButton.
/// </summary>
public partial class ToolStripSplitCheckButton : ToolStripSplitButton
{
//==============================================================================
// Inner class: ToolBarButonSplitCheckButtonEventArgs
//==============================================================================
/// <summary>
/// The event args for the check button click event. To be able to use the OnCheckedChanged
/// event, we must also record a dummy button as well as this one (hack).
/// </summary>
public class ToolBarButonSplitCheckButtonEventArgs : ToolBarButtonClickEventArgs
{
/// <summary>
/// Constructor.
/// </summary>
/// <param name="split_button">The sender split check button</param>
public ToolBarButonSplitCheckButtonEventArgs(ToolStripSplitCheckButton split_button)
: base(new ToolBarButton("Dummy Button")) // Hack - Dummy Button is not used
{
SplitCheckButton = split_button;
}
/// <summary>
/// The ToolStripSplitCheckButton to be sent as an argument.
/// </summary>
public ToolStripSplitCheckButton SplitCheckButton { get; set; }
}
//==========================================================================
// Construction
public ToolStripSplitCheckButton()
{
m_checked = false;
m_mouse_over = false;
}
//==========================================================================
// Properties
/// <summary>
/// Indicates whether the button should toggle its Checked state on click.
/// </summary>
[Category("Behavior"),
Description("Indicates whether the item should toggle its selected state when clicked."),
DefaultValue(true)]
public bool CheckOnClick { get; set; }
/// <summary>
/// Indictates the Checked state of the button.
/// </summary>
[Category("Behavior"),
Description("Indicates whether the ToolStripSplitCheckButton is pressed in or not pressed in."),
DefaultValue(false)]
public bool Checked { get { return m_checked; } set { m_checked = value; } }
//==========================================================================
// Methods
/// <summary>
/// Toggle the click state on button click.
/// </summary>
protected override void OnButtonClick(EventArgs e)
{
if (CheckOnClick) {
m_checked = !m_checked;
// Raise the OnCheckStateChanged event when the button is clicked
if (OnCheckChanged != null) {
ToolBarButonSplitCheckButtonEventArgs args = new ToolBarButonSplitCheckButtonEventArgs(this);
OnCheckChanged(this, args);
}
}
base.OnButtonClick(e);
}
/// <summary>
/// On mouse enter, record that we are over the button.
/// </summary>
protected override void OnMouseEnter(EventArgs e)
{
m_mouse_over = true;
base.OnMouseEnter(e);
this.Invalidate();
}
/// <summary>
/// On mouse leave, record that we are no longer over the button.
/// </summary>
protected override void OnMouseLeave(EventArgs e)
{
m_mouse_over = false;
base.OnMouseLeave(e);
this.Invalidate();
}
/// <summary>
/// Paint the check highlight when required.
/// </summary>
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (m_checked) {
// I can't get the check + mouse over to render properly, so just give the button a colour fill - Hack
if (m_mouse_over) {
using (Brush brush = new SolidBrush(Color.FromArgb(64, SystemColors.MenuHighlight))) {
e.Graphics.FillRectangle(brush, ButtonBounds);
}
}
ControlPaint.DrawBorder(
e.Graphics,
e.ClipRectangle, // To draw around the button + drop-down
//this.ButtonBounds, // To draw only around the button
SystemColors.MenuHighlight,
ButtonBorderStyle.Solid);
}
}
//==========================================================================
// Member Variables
// The delegate that acts as a signature for the function that is ultimately called
// when the OnCheckChanged event is triggered.
public delegate void SplitCheckButtonEventHandler(object source, EventArgs e);
public event SplitCheckButtonEventHandler OnCheckChanged;
private bool m_checked;
private bool m_mouse_over;
}
要使用它,请处理此按钮的 OnCheckChanged 事件。
然而,这有一些笨拙的技巧。为了能够为复选按钮引发 OnCheckedChanged 事件,事件参数除了包含实际按钮外,还必须包含对未使用的虚拟按钮的引用。即使在尝试了各种渲染器之后,我也无法让选中的 + 鼠标悬停在渲染上与普通选中按钮的渲染完全相同,所以我只绘制了一个颜色叠加层,这几乎是正确的但不完全正确。