WinForms 用户控件具有导致 ToolStripDropDown 自动关闭的 ComboBox

WinForms User Control has ComboBox that causes ToolStripDropDown to auto-close

我有一个看起来像组合框的自定义 WinForms 用户控件,但它打开了一个 ToolStripDropDown,其中包含另一个名为 NumericFilterPanel 的自定义用户控件,它有一个复选框、一个组合框和一个文本框。


问题是,当用户单击并为嵌入在下拉控件中的组合框选择一个选项时,会导致父下拉列表隐藏。

我已经设置 ToolStripDropDown.AutoClose = false,它修复了原来的问题,但现在我很难检测到下拉列表失去焦点的所有情况,例如当用户单击父表单或切换时程式。有时下拉菜单仍然可见并位于最顶部。

有没有办法保持 AutoClose = true 并防止嵌入式组合框关闭父下拉菜单,或者有没有办法始终检测下拉菜单何时失去焦点以便我可以手动关闭它?

  using System;
  using System.Drawing;
  using System.Windows.Forms;

  namespace mviWinControls
  {
    public partial class NumericRangeDropDown : UserControl
    {
      private const int ARROW_HEIGHT = 4;
      private Brush arrowBrush = new SolidBrush(Color.FromArgb(77, 97, 133));

      private ToolStripDropDown _dropdown;
      private ToolStripControlHost _host;
      private NumericFilterPanel _filter;

      public NumericRangeDropDown()
      {
        InitializeComponent();

        _filter = new NumericFilterPanel();
        _filter.DropDown = this;

        _host = new ToolStripControlHost(_filter);
        _host.Margin = Padding.Empty;
        _host.Padding = Padding.Empty;

        _dropdown = new ToolStripDropDown();
        _dropdown.Margin = Padding.Empty;
        _dropdown.Padding = Padding.Empty;
        _dropdown.AutoClose = false;  // Use this because panel has a combobox.  https://social.msdn.microsoft.com/Forums/windows/en-US/dd95b982-820e-4807-8a1f-79c74acab3f8/two-problems-toolstripdropdown?forum=winforms
        _dropdown.Items.Add(_host);
        _dropdown.Leave += new System.EventHandler(this.DropDown_Leave);

        this.Leave += new System.EventHandler(this.DropDown_Leave);
      }

      /// <summary> 
      /// Clean up any resources being used.
      /// </summary>
      /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
      protected override void Dispose(bool disposing)
      {
        if (disposing)
        {
          if (components != null) components.Dispose();
          if (_dropdown != null) _dropdown.Dispose();
        }
        base.Dispose(disposing);
      }

      public override string Text
      {
        get
        {
          return base.Text;
        }
        set
        {
          base.Text = value;
          _filter.SetValue(value);
        }
      }

      protected override void OnPaint(PaintEventArgs e)
      {
        //base.OnPaint(e);
        TextBox _txtDraw = new TextBox();

        _txtDraw.Width = this.Width;

        using (Bitmap bmp = new Bitmap(_txtDraw.Width, _txtDraw.Height))
        {
          _txtDraw.DrawToBitmap(bmp, new Rectangle(0, 0, _txtDraw.Width, _txtDraw.Height));
          e.Graphics.DrawImage(bmp, 0, 0);
        }

        StringFormat format = new StringFormat();
        format.Alignment = StringAlignment.Near;
        format.FormatFlags = StringFormatFlags.NoWrap;
        format.LineAlignment = StringAlignment.Center;

        using (Brush b = new SolidBrush(this.ForeColor))
          e.Graphics.DrawString(this.Text, this.Font, b, this.DisplayRectangle, format);

        Point[] arrowPoints = new Point[] { new Point(this.Width - ARROW_HEIGHT * 3 - 2, (this.Height - ARROW_HEIGHT) / 2),
                                            new Point(this.Width - ARROW_HEIGHT + 1 - 2, (this.Height - ARROW_HEIGHT) / 2),
                                            new Point(this.Width - ARROW_HEIGHT * 2 - 2, this.Height - (this.Height - ARROW_HEIGHT) / 2) };

        e.Graphics.FillPolygon(arrowBrush, arrowPoints );

      }

      private void DropDown_Leave(object sender, EventArgs e)
      {
        HideDropDown();
        this.Text = _filter.SummaryText();
      }

      private void NumericRangeDropDown_Click(object sender, EventArgs e)
      {
        if (_dropdown.Visible)
          HideDropDown();
        else
          ShowDropDown();
      }

      public void ShowDropDown()
      {
        _dropdown.Show(this, new Point(0, this.Height), ToolStripDropDownDirection.Default);
        _dropdown.BringToFront();
        //_dropdown.Focus();
        _filter.Select();
        _filter.Focus();
      }

      public void HideDropDown()
      {
        _dropdown.Close();
        this.Invalidate();
      }

    }
  }

这是一个组合框,可以为您自动禁用和启用主机控件上的自动关闭 属性。

来源(我针对组合框修改了它,而不是在他们的示例中使用 DatePicker):http://www.queasy.me/programming/questions/13919634/tool+strip+toolstripdropdownbutton+close+and+lose+window+focus

public partial class CComboBox : ComboBox
{
    private bool savedAutoClose;

    public CComboBox()
    {
        InitializeComponent();
    }

    protected override void OnDropDownClosed(EventArgs e)
    {
        if (this.Parent != null)
        {
            var dropDownHost = this.Parent.Parent as ToolStripDropDown; // recursive instead?
            if (dropDownHost != null)
                dropDownHost.AutoClose = savedAutoClose; // restore the parent's AutoClose preference
        }

        base.OnDropDownClosed(e);
    }

    protected override void OnDropDown(EventArgs e)
    {
        if (this.Parent != null)
        {
            var dropDownHost = this.Parent.Parent as ToolStripDropDown; // recursive instead?
            if (dropDownHost != null)
            {
                savedAutoClose = dropDownHost.AutoClose;
                // ensure that our parent doesn't close while the calendar is open
                dropDownHost.AutoClose = false;
            }
        }
        base.OnDropDown(e);
    }
}

仔细查看源代码后,错误(确实是错误)在于 ToolStripManager,它设置了一个消息过滤器以捕获活动 ToolStrip,检查点击是否在子 window.

的范围内

问题是它使用 activeToolStrip.ClientRectangle 来验证这一点,而不检查被单击的 window 的子 windows。在 ComboBox 的情况下,下拉菜单是一个单独的子项 window,它漂浮在所有内容之上,并且实际上可以超出主组合 window 的范围,如果下拉 -羽绒服很大

The relevant line is:

if (!activeToolStrip.ClientRectangle.Contains(pt.x, pt.y)) {

我找到了另一种解决方案,可以在下拉菜单打开时暂时禁用自动关闭。

理想情况下,您应该在 ToolStrip 中使用 ToolStripComboBox 而不是仅使用 ComboBox。但是如果你只想我们一个,你可以添加事件来调用相关的私有方法来暂停和恢复消息过滤器。

static class ToolStripComboBoxFilter
{
    private static Action SuspendMenuMode = (Action) typeof(ToolStripManager)
        .GetNestedType("ModalMenuFilter", BindingFlags.NonPublic)
        .GetMethod(nameof(SuspendMenuMode), BindingFlags.NonPublic | BindingFlags.Static)
        .CreateDelegate(typeof(Action));

    private static Action ResumeMenuMode = (Action)typeof(ToolStripManager)
        .GetNestedType("ModalMenuFilter", BindingFlags.NonPublic)
        .GetMethod(nameof(ResumeMenuMode), BindingFlags.NonPublic | BindingFlags.Static)
        .CreateDelegate(typeof(Action));

    public static void AddToolStripFilterEvents(this ComboBox combo)
    {
        combo.DropDown += OnDropDown;
        combo.DropDownClosed += OnDropDownClosed;
    }

    private static void OnDropDown(object sender, EventArgs e)
    {
        SuspendMenuMode();
    }

    private static void OnDropDownClosed(object sender, EventArgs e)
    {
        ResumeMenuMode();
    }
}

你可以这样使用它

myComboBox.AddToolStripFilterEvents();