【问题标题】:WinForms User Control has ComboBox that causes ToolStripDropDown to auto-closeWinForms 用户控件具有导致 ToolStripDropDown 自动关闭的 ComboBox
【发布时间】:2018-10-04 00:10:57
【问题描述】:

我有一个自定义 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();
      }

    }
  }

【问题讨论】:

  • 我希望我能找到同样问题的答案
  • Nvm,找到了一个可行的解决方案。在下面检查我的答案。

标签: c# winforms combobox custom-controls toolstripdropdown


【解决方案1】:

仔细查看源代码后,错误(它是一个错误)在于ToolStripManager 设置了一个消息过滤器以捕获活动ToolStrip 之外的鼠标点击,检查如果点击在子窗口的范围内。

问题是它使用activeToolStrip.ClientRectangle 来验证这一点,并且不检查被点击窗口的子窗口。对于ComboBox,下拉菜单是一个单独的子窗口,它浮动在所有内容之上,如果下拉菜单很大,实际上可能超出主组合窗口的范围。

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();

【讨论】:

    【解决方案2】:

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

    来源(我在他们的示例中将其修改为组合框而不是 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);
        }
    }
    

    【讨论】:

    • 谢谢,这段代码解决了!我只需要更改父类的类型以匹配我的 NumericRangeDropDown 类并添加一个属性来获取/设置 _dropdown 对象的 AutoClose。
    猜你喜欢
    • 2014-09-05
    • 1970-01-01
    • 2011-12-17
    • 1970-01-01
    • 1970-01-01
    • 2011-06-27
    • 1970-01-01
    • 2011-01-12
    • 1970-01-01
    相关资源
    最近更新 更多