Visual Studio样式的撤消下拉按钮 - 自定义ToolStripSplitButton

时间:2011-11-10 04:36:06

标签: c# winforms

我希望实现一个Visual Studio风格的撤消下拉按钮:

Undo drop-down

我已经浏览了整个互联网,似乎找不到任何真正的实现。

我是从ToolStripSplitButton派生出来的,但并不知道从哪里去。它的DropDown属性是ToolStripDropDown,但似乎没有任何关于选中多个项目的内容,更不用说滚动了,还有底部的文字。

因此,我不是默认的ToolStripDropDown,而是认为整个下拉部分应该是基于组合框的自定义控件。那么问题是,如何使右侧(下拉箭头)按钮执行除显示其默认下拉菜单之外的其他操作?

我在这里走在正确的轨道上吗?谢谢!

4 个答案:

答案 0 :(得分:4)

是的,我认为你走在了正确的轨道上。在这种情况下,ToolStripControlHost是您的朋友。

您不一定需要从中获取(除非您自己控制),但尝试只订阅ToolStripSplitButton's DropDownOpening事件:

工作示例:

private ListBox listBox1;

public Form1()
{
  InitializeComponent();

  listBox1 = new ListBox();
  listBox1.IntegralHeight = false;
  listBox1.MinimumSize = new Size(120, 120);  \\ <- important
  listBox1.Items.Add("Item 1");
  listBox1.Items.Add("Item 2");
}

private void toolStripSplitButton1_DropDownOpening(object sender, EventArgs e) {
  ToolStripControlHost toolHost = new ToolStripControlHost(listBox1);
  toolHost.Size = new Size(120, 120);
  toolHost.Margin = new Padding(0);
  ToolStripDropDown toolDrop = new ToolStripDropDown();
  toolDrop.Padding = new Padding(0);
  toolDrop.Items.Add(toolHost);
  toolDrop.Show(this, new Point(toolStripSplitButton1.Bounds.Left,
                                toolStripSplitButton1.Bounds.Bottom));
}

结果如下:

enter image description here

对于您的应用,您需要将ListBox替换为您自己的UserControl,这样您就可以包含您想要的任何内容。 ToolStripControlHost只能容纳一个控件,设置MinimumSize属性非常重要,否则删除的控件的大小不正确。

答案 1 :(得分:4)

非常感谢LarsTech! (几小时前我还不知道ToolStripControlHost)

这是我的实现,它非常接近VS下拉...

UndoRedoDropDown

你应该可以放弃这个代表&amp;功能进入你的表格:

    public delegate void UndoRedoCallback(int count);

    private void DrawDropDown(ToolStripSplitButton button, string action, IEnumerable<string> commands, UndoRedoCallback callback)
    {
        int width = 277;
        int listHeight = 181;
        int textHeight = 29;

        Panel panel = new Panel()
        {
            Size = new Size(width, textHeight + listHeight),
            Padding = new Padding(0),
            Margin = new Padding(0),
            BorderStyle = BorderStyle.FixedSingle,
        };
        Label label = new Label()
        {
            Size = new Size(width, textHeight),
            Location = new Point(1, listHeight - 2),
            TextAlign = ContentAlignment.MiddleCenter,
            Text = String.Format("{0} 1 Action", action),
            Padding = new Padding(0),
            Margin = new Padding(0),
        };
        ListBox list = new ListBox()
        {
            Size = new Size(width, listHeight),
            Location = new Point(1,1),
            SelectionMode = SelectionMode.MultiSimple,
            ScrollAlwaysVisible = true,
            Padding = new Padding(0),
            Margin = new Padding(0),
            BorderStyle = BorderStyle.None,
            Font = new Font(panel.Font.FontFamily, 9),
        };
        foreach (var item in commands) { list.Items.Add(item); }
        if (list.Items.Count == 0) return;
        list.SelectedIndex = 0;

        ToolStripControlHost toolHost = new ToolStripControlHost(panel)
        {
            Size = panel.Size,
            Margin = new Padding(0),
        };
        ToolStripDropDown toolDrop = new ToolStripDropDown()
        {
            Padding = new Padding(0),
        };
        toolDrop.Items.Add(toolHost);

        panel.Controls.Add(list);
        panel.Controls.Add(label);
        toolDrop.Show(this, new Point(button.Bounds.Left + button.Owner.Left, button.Bounds.Bottom + button.Owner.Top));

        // *Note: These will be "up values" that will exist beyond the scope of this function
        int index = 1;
        int lastIndex = 1;

        list.Click += (sender, e) => { toolDrop.Close(); callback(index); };
        list.MouseMove += (sender, e) =>
        {
            index = Math.Max(1, list.IndexFromPoint(e.Location) + 1);
            if (lastIndex != index)
            {
                int topIndex = Math.Max(0, Math.Min(list.TopIndex + e.Delta, list.Items.Count - 1));
                list.BeginUpdate();
                list.ClearSelected();
                for (int i = 0; i < index; ++i) { list.SelectedIndex = i; }
                label.Text = String.Format("{0} {1} Action{2}", action, index, index == 1 ? "" : "s");
                lastIndex = index;
                list.EndUpdate();
                list.TopIndex = topIndex;
            }
        };
        list.Focus();
    }

你可以设置它并像这样进行测试,假设你有一个空白表单(Form1),其中toolStrip添加了1个ToolStripSplitButton(toolStripSplitButton1):

    public Form1()
    {
        InitializeComponent();

        // Call DrawDropDown with:
        //   The clicked ToolStripSplitButton
        //   "Undo" as the action
        //   TestDropDown for the enumerable string source for the list box
        //   UndoCommands for the click callback
        toolStripSplitButton1.DropDownOpening += (sender, e) => { DrawDropDown(
            toolStripSplitButton1,
            "Undo",
            TestDropDown,
            UndoCommands
        ); };
    }


    private IEnumerable<string> TestDropDown
    {
        // Provides a list of strings for testing the drop down
        get { for (int i = 1; i < 1000; ++i) { yield return "test " + i; } }
    }

    private void UndoCommands(int count)
    {
        // Do something with the count when an action is clicked
        Console.WriteLine("Undo: {0}", count);
    }

以下是使用撤消/重做系统的更好示例:http://www.codeproject.com/KB/cs/AutomatingUndoRedo.aspx

    public Form1()
    {
        InitializeComponent();

        // Call DrawDropDown with:
        //   The Undo ToolStripSplitButton button on the Standard tool strip
        //   "Undo" as the action name
        //   The list of UndoCommands from the UndoRedoManager
        //   The Undo method of the UndoRedoManager
        m_TSSB_Standard_Undo.DropDownOpening += (sender, e) => { DrawDropDown(
            m_TSSB_Standard_Undo,
            "Undo",
            UndoRedoManager.UndoCommands,
            UndoRedoManager.Undo
        ); };
    }

*注意:我确实修改了撤消&amp; UndoRedoManager中的重做方法接受计数:

    // Based on code by Siarhei Arkhipenka (Sergey Arhipenko) (http://www.codeproject.com/KB/cs/AutomatingUndoRedo.aspx)
    public static void Undo(int count)
    {
        AssertNoCommand();
        if (CanUndo == false) return;
        for (int i = 0; (i < count) && CanUndo; ++i)
        {
            Command command = history[currentPosition--];
            foreach (IUndoRedo member in command.Keys)
            {
                member.OnUndo(command[member]);
            }
        }
        OnCommandDone(CommandDoneType.Undo);
    }

答案 2 :(得分:0)

Vs 2010是一个WPF应用程序。如果您在此应用程序开发的开始,那么使用WPF作为核心技术。 WPF下拉按钮在WPF ribbon中实现。源代码可在CodePlex上获得。

答案 3 :(得分:0)

我建议单独从工具栏按钮实现弹出窗口。弹出窗口是单独的窗口,顶部标记在失去焦点或按下转义时自动关闭。如果你编写自己的弹出窗口代码,那么你就不必使你的行为适合预先存在的模型(在你的情况下会很难)。只需使用列表框和状态栏创建一个新的最顶层窗口,然后您可以根据需要在列表框中自由实现选择行为。