1

I have created my own ComboBox-like control where the dropdown part contains a tree. I have seen those solutions using an ordinary ComboBox and overwriting the WndProc, but there was always some odd behavior despite lots and lots of code. So I decided to make it simple: just a label with a ToolStripDropDown/ToolStripControlHost that is opened when mouse goes down on the label. The missing ComboBox triangle doesn't hurt.

Everything works perfectly, except one tiny thing: like with the stock ComboBox I would like the dropdown to hide when I click on the label again. But when I click on it, the dropdown hides for a split second, just to appear again. If I click outside the label, the dropdown just hides, like it should be.

public class RepoNodeComboBox: Label
{
    RepoTreeView repoTreeView;
    ToolStripControlHost treeViewHost;
    ToolStripDropDown dropDown;
    bool isDropDownOpen;

    public int DropDownHeight;

    public RepoNodeComboBox()
    {
        repoTreeView = new RepoTreeView();
        repoTreeView.BorderStyle = BorderStyle.None;
        repoTreeView.LabelEdit = false;

        treeViewHost = new ToolStripControlHost(repoTreeView);
        treeViewHost.Margin = Padding.Empty;
        treeViewHost.Padding = Padding.Empty;
        treeViewHost.AutoSize = false;

        dropDown = new ToolStripDropDown();
        dropDown.CanOverflow = true;
        dropDown.AutoClose = true;
        dropDown.DropShadowEnabled = true;
        dropDown.Items.Add(treeViewHost);
        dropDown.Closing += dropDownClosing;

        TextAlign = ContentAlignment.MiddleLeft;
        BackColor = SystemColors.Window;
        BorderStyle = BorderStyle.FixedSingle;

        DropDownHeight = 400;
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing) 
        {
            if (dropDown != null) 
            {
                dropDown.Dispose();
                dropDown = null;
            }
        }
        base.Dispose(disposing);
    }

    // when mouse goes down on the label, this is executed first            
    void dropDownClosing(object sender, System.ComponentModel.CancelEventArgs e)
    {
        // just to test if I can get more out of it than with dropdown.Visible
        isDropDownOpen = false; 
    }

    // this is subsidiary to the Closing event of the dropdown
    protected override void OnMouseDown(MouseEventArgs e)
    {
        if (dropDown != null) 
        {
            if (isDropDownOpen)
            {
                dropDown.Hide();
            }
            else
            {
                repoTreeView.Size = new Size(Width, DropDownHeight);
                treeViewHost.Width = Width;
                treeViewHost.Height = DropDownHeight;
                dropDown.Show(this, 0, Height);
                isDropDownOpen = true;
            }
        }

        base.OnMouseDown(e);
    }
}

As far as I can see (breakpoints), the dropdown catches the MOUSEDOWN event first in order to close itself. Only after that my label gets passed through the MOUSEDOWN event, and since it sees the dropdown is closed, it thinks the label has been clicked like for the first time - and opens the dropdown again.

So it seems the label has no chance of knowing if the MOUSEDOWN was the result of closing the dropdown item. I could open it every other event, but that would require no other closing events to happen.

Is there any way to make sure that an open dropdown item just closes even if I click on the label?

LarsTech
  • 80,625
  • 14
  • 153
  • 225
oliver
  • 2,771
  • 15
  • 32
  • Would be easier to test if you gave enough code run it. – LarsTech Sep 18 '18 at 18:32
  • True, but difficult because RepoTreeView and RepoNode (and descendants) are a lot of classes and they are proprietary so I am not supposed to post them here. – oliver Sep 18 '18 at 18:35
  • Minimal Code to duplicate the issue. At least give us the host and drop down code. – LarsTech Sep 18 '18 at 18:37
  • See my edit. I hope that's about it. So RepoTreeView could be replaced by any other control like picture box or something. – oliver Sep 18 '18 at 18:47

1 Answers1

1

Try checking for the Mouse position when the floating host closes:

void dropDownClosing(object sender, CancelEventArgs e) {
  isDropDownOpen = this.ClientRectangle.Contains(this.PointToClient(Control.MousePosition));
}

On the MouseDown code, make sure to set isDropDownOpen to false in the true block:

protected override void OnMouseDown(MouseEventArgs e) {
  if (dropDown != null) {
    if (isDropDownOpen) {
      isDropDownOpen = false;
      dropDown.Hide();
    } else {
      isDropDownOpen = true;
      repoTreeView.Size = new Size(Width, DropDownHeight);
      treeViewHost.Width = Width;
      treeViewHost.Height = DropDownHeight;
      dropDown.Show(this, 0, Height);
    }
  }
  base.OnMouseDown(e);
}
LarsTech
  • 80,625
  • 14
  • 153
  • 225
  • Super cool, works like a charm, thanks! I already tried checking mouse position, but something must have been in the wrong order or wrong coordinate system with my attempt, I guess. – oliver Sep 18 '18 at 19:32
  • @oliver Those PointToClient and PointToScreen calls are trickier than they appear. – LarsTech Sep 18 '18 at 19:47