Search code examples
c#winformscomboboxuser-controlsdropdown

C# Custom ComboBox - DropDown Position


I'm creating a ComboBox control using ToolStripControlHost and ToolStripDropDown that can host any kind of control in the DropDown window. For example, the DropDown window might display a listview or treeview or even another usercontrol.

I'm posting a simplified code below where dropdown host a usercontrol with a listview and a button like this:

enter image description here

The problem occurs when the control is positioned at the bottom of the screen in such a way that the dropdown window will extrapolate the lower boundary of the screen. When this occurs, the dropdown ends up hiding the control.

In this case, I'd like to fix the _dropDown.Show method call to show dropdown window as follows:

enter image description here

To repeat the problem, just run the code below and drag the window to the bottom of the screen and open the dropdown.

using System;
using System.Windows.Forms;

public class CustomComboBox : UserControl
{
    ToolStripDropDown _dropDown;

    public CustomComboBox()
    {
        var textbox = new TextBox();
        textbox.Location = new System.Drawing.Point(0, 0);
        textbox.Size = new System.Drawing.Size(this.Width - 22, 20);
        textbox.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top;
        this.Controls.Add(textbox);

        var button = new Button();
        button.Location = new System.Drawing.Point(this.Width - 22, -1);
        button.Size = new System.Drawing.Size(22, 22);
        button.Text = "\u2BC6"; 
        button.Anchor = AnchorStyles.Right | AnchorStyles.Top;
        button.Click += new System.EventHandler(this.Button_Click);
        this.Controls.Add(button);

        var dropDownControl = new DropDownControlTest();

        var controlHost = new ToolStripControlHost(dropDownControl);

        _dropDown = new ToolStripDropDown();
        _dropDown.AutoSize = true;              
        _dropDown.Items.Add(controlHost); 
    }

    void Button_Click(object sender, EventArgs e)
    {
        _dropDown.Show(this, 0, this.Height);
    }
}

public class DropDownControlTest : UserControl
{
    public DropDownControlTest()
    {
        var listview = new ListView();
        listview.Location = new System.Drawing.Point(3, 1);
        listview.Size =  new System.Drawing.Size(400,300);
        listview.View = View.Details;
        listview.Columns.Add("Col 1",100);
        listview.Columns.Add("Col 2",100);
        this.Controls.Add(listview);

        var button = new Button();
        button.Location = new System.Drawing.Point(3, 305);
        button.Text = "More...";
        this.Controls.Add(button);
    }
}

public class Form1 : Form
{
    private static void Main(string[] args)
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }

    public Form1 ()
    {
        CustomComboBox ccBox = new CustomComboBox();
        ccBox.Location =  new System.Drawing.Point(10, 10);
        ccBox.Height = 20;

        this.Text = "Test CustomComboBox";
        this.Controls.Add(ccBox);
    }
}

Solution

  • You can use the ToolStripDropDown.Show Method (Control, Point, ToolStripDropDownDirection) overload to control the drop direction. The code will need to perform bounds checking to decide whether to place the dropdown above or below the textbox.

    The following is a simplistic method for doing the bounds checking and was only tested on a single screen configuration.

    First, make textbox a class level variable.

    private TextBox textbox;
    public CustomComboBox()
        {
        //var textbox = new TextBox();
        textbox = new TextBox();
    

    The display logic is as follows.

    void Button_Click(object sender, EventArgs e)
        {
        Point textBoxScreenLocation = textbox.PointToScreen(textbox.Location);
    
        // try to position _dropDown below textbox
        Point pt = textBoxScreenLocation;
        pt.Offset(0, textbox.Height);
    
        // determine if it will fit on the screen below the textbox
        Size dropdownSize = _dropDown.GetPreferredSize(Size.Empty);
        Rectangle dropdownBounds = new Rectangle(pt, dropdownSize);
    
        if (dropdownBounds.Bottom <= Screen.GetWorkingArea(dropdownBounds).Bottom)
            {   // show below
                _dropDown.Show(pt, ToolStripDropDownDirection.BelowRight);
            }
        else
            {   // show above
                _dropDown.Show(textBoxScreenLocation, ToolStripDropDownDirection.AboveRight);
            }
        }
    }