0

I would like to resize any ComboBox dropdown width based on the longest string in the dropdown items. I want this to work on any ComboBox with items which means one whose items are strings, some object with DisplayMember set, or a DataTable. I found some code which works for strings, but not when a DisplayMember is set

static void resizeCombo(ComboBox cb)
{
    cb.DropDownWidth = cb.Items.Cast<string>().Max(x => TextRenderer.MeasureText(x, cb.Font).Width);
}

In the following three examples, the first which is just strings will work, but the following two don't work (cb.Items.Cast<string>() cast to string fails), and demonstrate that the DisplayMember can vary when bound to sources of different classes, so I can't just use "Name" for example

var c = new string[] { "Name1", "Name2" };
comboBox.DataSource = c.ToList();
resizeCombo(comboBox);

var c1 = new Class1[] { new Class1() { ID = 1, Name1 = "Name1" }, new Class1() { ID = 2, Name1 = "Name2" } };
comboBox1.DisplayMember = "Name1";
comboBox1.ValueMember = "ID";
comboBox1.DataSource = c1.ToList();
resizeCombo(comboBox1);

var c2 = new Class2[] { new Class2() { ID = 2, Name2 = "Name1" }, new Class2() { ID = 2, Name2 = "Name2" } };
comboBox2.DisplayMember = "Name2";
comboBox2.ValueMember = "ID";
comboBox2.DataSource = c2.ToList();
resizeCombo(comboBox2);

I could reflect the DisplayMember, and find the strings by name, and it may solve the List<class> case, but not DataTable.

I am looking for a method to get all the strings in the ComboBox regardless of how they are added. Is there one?

djv
  • 15,168
  • 7
  • 48
  • 72

1 Answers1

1

Combobox has GetItemText method that returns the string representation of item.

This should work:

    static void resizeCombo(ComboBox cb)
    {
        if (cb.Items.Count == 0) cb.DropDownWidth = cb.Width;
        else
        {
            int maxWidth = int.MinValue;
            for (int i = 0; i < cb.Items.Count; i++)
            {
                maxWidth = Math.Max(maxWidth, TextRenderer.MeasureText(cb.GetItemText(cb.Items[i]), cb.Font).Width);
            }
            if (cb.Items.Count > cb.MaxDropDownItems) maxWidth += SystemInformation.VerticalScrollBarWidth                    
            cb.DropDownWidth = maxWidth;
        }
    }

To test with DataTable:

        DataTable t = new DataTable();
        t.Columns.Add(new DataColumn("ID", typeof(int)));
        t.Columns.Add(new DataColumn("Name2", typeof(string)));

        t.Rows.Add(new object[] { 1, "Somename" });
        t.Rows.Add(new object[] { 2, "Some other name" });

        comboBox2.DisplayMember = "Name2";
        comboBox2.ValueMember = "ID";
        comboBox2.DataSource = t;
        resizeCombo(comboBox2);

NB! For this to work, do not use the resize function in a form's constructor. Use it from the Load event or similar, once the form is already up and running.

Avo Nappo
  • 620
  • 4
  • 9
  • That works great thank you – djv Sep 27 '21 at 16:58
  • How can this works with a Datatable binded to the Datasource? Items is empty in that case. – Steve Sep 27 '21 at 16:59
  • Nope, this works with DataTable as source as well. Try it. – Avo Nappo Sep 27 '21 at 17:13
  • Sorry but I can't make this work with a Winforms combobox binded to a DataTable. Just to start the if results in zero items. Then setting the DropDownWidth to zero raises an exception as documented here https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.combobox.dropdownwidth?view=net-5.0#exceptions So I really wonder how you make it work – Steve Sep 27 '21 at 17:25
  • Added DataTable test code I used to the answer. But yes, setting the width to 0 was a silly idea. Should just reset it to combobox's width there. – Avo Nappo Sep 27 '21 at 17:34
  • I got it. It works only if you resize the combo after displaying the form. You cannot do that in the form constructor. – Steve Sep 27 '21 at 17:36
  • Excellent point, thanks. Added that as a warning to the answer. – Avo Nappo Sep 27 '21 at 17:42
  • @AvoNappo I should add I removed that line setting width to zero in my own code. – djv Sep 27 '21 at 18:17
  • This does not work well when the vertical scroll is visible. That case needs to be handled explicitly. – djv Sep 27 '21 at 18:18
  • This takes care of case with vertical scroll: `var mt = TextRenderer.MeasureText(cb.GetItemText(cb.Items[i]), cb.Font);` `if(mt.Height * cb.Items.Count > cb.DropDownHeight) additionalPixels = widthOfVerticalScrollbar;` `cb.DropDownWidth = maxWidth + additionalPixels;` – djv Sep 27 '21 at 18:22
  • `SystemInformation` class has a constant for that width. So you could do it like this as well (after iteration over items): `if (cb.Items.Count > cb.MaxDropDownItems) maxWidth += SystemInformation.VerticalScrollBarWidth;` – Avo Nappo Sep 27 '21 at 18:36
  • @AvoNappo great, I couldn't find that constant. Thanks – djv Sep 27 '21 at 18:58