Search code examples
c#winformscomboboxcustom-controlstoolstripdropdown

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


I have a custom WinForms user control that looks like a combobox but instead opens a ToolStripDropDown that contains another custom user control, called NumericFilterPanel, that has a checkbox, a combobox, and a textbox.

enter image description here

The problem is that when the user click-selects an option for the combobox embedded in the dropdown control, it causes the parent dropdown to hide.

I have set ToolStripDropDown.AutoClose = false, which fixes the original problem, but now I am having difficulty detecting all the situations where the dropdown loses focus, such as when the user clicks on the parent form or switches programs. Sometimes the dropdown remains visible and topmost.

Is there a way to either keep AutoClose = true and prevent the embedded combobox from closing the parent dropdown, or is there a way to always detect when the dropdown has lost focus so I can manually close it?

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

    }
  }

Solution

  • Here's a combobox that can automatically disable and enable the AutoClose property on the host control for you.

    Source(I modified it for a combobox versus the DatePicker in their example): 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);
        }
    }