The Windows Forms ComboBox control is an excellent user interface component for situations where you want to present the user with a discrete set of choices, or invite them to enter an arbitrary string value. It does not pose much of a usability challenge; inherently, the control is intuitive and easy to use. However, there are occasions when the usability of an application can be greatly improved by arranging the user’s options into groups or categories. This is particularly useful when the number of options is large, or when there is a risk of ambiguity in the wording of the options – often GUI designers are locked in a constant battle between brevity and elegance over the risk of ambiguity. By introducing groups/categories, the brevity can be maintained while adding another layer of description.

Unfortunately, the standard ComboBox control has no provision for groups or categories; it is designed to be a flat list with no implied cohesion between the list items, and no hierarchy. Nevertheless, a number of applications (including Mozilla Firefox) have identified the need for a drop-down control with a grouping mechanism, and there are now many examples floating around in application land.

I have written GroupedComboBox in an attempt to provide a solution that is in-keeping with the existing principles of visual design and data binding used throughout Windows Forms. It is a drop-in replacement for the ComboBox, inheriting from it in order to reduce functional duplication. As is the case with much of the extended functionality in the standard ComboBox, the control must be bound to a data source in order to take advantage of grouping. Very little changes to existing code are required in order to use the grouping mechanism:

  • Set the value of the new GroupMember property to the name of the property you wish to group the list items by. (This functions in the same manner as the DisplayMember and ValueMember properties, so you can use a property, column name, etc)
  • Bind to a suitable data source (Array, IList, IListSource, ITypedList, DataView, etc). (Note: Arrays must be strongly-typed; object[] data sources expose no bindable properties.)
  • Due to the implementation, the items in the list will be inherently sorted. Lexiographical sorting is considered to be an aspect of best-practice user interface design. If you have different sorting requirements, consider using a more appropriate control.
Comparison showing GroupedComboBox and ComboBox bound to the same data source.

Note: The visual style renderer on Windows Vista/7 uses a raised button effect when the DropDownStyle property is set to ComboBoxStyle.DropDownList and the DrawMode property is set to DrawMode.Normal, however it reverts to the classic rendering style when owner-drawing is enabled. There is no simple way to force the system style in this situation, therefore GroupedComboBox may visually clash with System.Windows.Forms.ComboBox if both types of controls appear on the same Form.

Some Background on Windows Forms Data Binding

If you use the data binding features in Windows Forms controls, you may be curious to know how values can be plucked from a data source using the DisplayMember/ValueMember/DataSource mechanism. The items in a data source are not of any one type; depending on the source, they can be primitive data types, objects with public properties, instances of System.Data.DataRowView or even a heterogenous collection of different types of items. The only constraint on data sources, in fact, is that they implement IList or IListSource.

So, given an object of an unpredictable type, how can we get the value of one of its properties? Well, firstly we need to understand the definition of a ‘property’ in the context of data binding; it does not necessarily refer to a get/set-style property, but rather an abstraction of one. This is what allows us to bind to columns in a DataView, or properties that may or may not exist in a heterogenous collection. The System.ComponentModel.PropertyDescriptor class is the mechanism we use to access the values of these abstracted properties.

PropertyDescriptor is an abstract class. Property descriptors are generally obtained from the data source, rather than from the list items themselves.  There are different implementations for different data sources; for example, System.ComponentModel.ReflectPropertyDescriptor uses Reflection to crawl a collection’s element types for their public properties, while the DataView uses System.Data.DataColumnPropertyDescriptor to represent its columns.

You can get property descriptors in a number of ways – sources that implement ITypedList expose them directly, while arrays and lists require the ListBindingHelper to retrieve them. Thankfully, the BindingSource component rounds them all up via its GetItemProperties() method. In situations (such as this example) where you have the name of the desired property as a string, you need simply enumerate through the list of property descriptors until you find one with a matching Name value.

// assume 'dataSource' is a data source that implements IList or IListSource and
// 'displayMember' is a string representing the property we want
BindingSource bindingSource = new BindingSource(dataSource, String.Empty);
PropertyDescriptor displayProperty = null;
foreach (PropertyDescriptor descriptor in bindingSource.GetItemProperties(null)) {
    if (descriptor.Name.Equals(displayMember)) {
        displayProperty = descriptor;
        break;
    }
}

Once you have an instance of PropertyDescriptor, you can call its GetValue() method for any item in the data source:

// assume 'listItem' is an object obtained from the data source
object displayValue = (displayProperty != null) ? displayProperty.GetValue(listItem) : null;

How GroupedComboBox Implements GroupMember

Bearing in mind the above, it is not difficult to extend this functionality to obtain a group-by value for our data source. My implementation shadows the ComboBox control’s DataSource property to transparently ensure that a BindingSource is used:

public new object DataSource {
    get {
        return (mBindingSource != null) ? mBindingSource.DataSource : null;
    }
    set {
        mBindingSource = (value != null) ? new BindingSource(value, String.Empty) : null;
    }
}

Having established that a BindingSource component will be available, we can look up the group-by value of each list item by matching the value of GroupMember to the Name property of one of the binding source’s PropertyDescriptor objects. There is, of course, always the chance that the data source will not have a matching property – should this happen, we will assume that a given list item is ungrouped.

Sorting List Items Into Groups

In order to be presented in groups, the list items must be specially sorted – firstly by group (if the list item is grouped), then by display value. We cannot assume that the data source is pre-sorted in this manner, or that it is even sortable at all, therefore we will use an internal sorted collection to enforce the order of the list items. For data sources containing reference types, there is only a small overhead in doing so (though for data sources containing value types, we are essentially duplicating the list). It is this sorted collection that the underlying ComboBox is bound to.

Since we are creating a sorted view of the original data source, the onice is on GroupedComboBox to stay in-sync with it. This is another compelling reason to use a BindingSource, as it provides the ListChanged event for compatible data sources (e.g. DataView). The sorted collection is rebuilt whenever the data source or GroupMember property is changed.

The two-tier sort is performed in the following manner (where ‘mGroupProperty‘ is the PropertyDescriptor object obtained earlier):

int IComparer.Compare(object x, object y) {
    int secondLevelSort = Comparer.Default.Compare(GetItemText(x), GetItemText(y));
    if (mGroupProperty == null) return secondLevelSort;

    int firstLevelSort = Comparer.Default.Compare(
        Convert.ToString(mGroupProperty.GetValue(x)),
        Convert.ToString(mGroupProperty.GetValue(y))
    );

    return (firstLevelSort == 0) ? secondLevelSort : firstLevelSort;
}

The most sensible collection to use internally in this case would be ArrayList, since we must select an IList, we know very little about the elements and we want to sort them.

Painting the List Items

To display the list items in a grouped form, we will have to paint them manually. This is not a hugely daunting task, but care must be taken in order to avoid exception-prone code and maintain the visual principles of Windows Forms controls. We will use the OwnerDrawVariable mode to paint the items, because it allows us to have some items drawn larger than others; in fact, the first item in each group will be twice as tall in order to accommodate the group header.

Given its index in the Items collection, a list item requires a header if:

  • It is at position 0 and has a non-empty group-by value, or:
  • It has a different group-by value to the item at [index – 1].

The height of an item in the drop-down portion of the ComboBox is the number of lines multiplied by the Height property of the Font. We calculate the size of each list item by handling the MeasureItem event. The item is then painted in the handler for the DrawItem event.

The TextRenderer class is very useful in rendering each item. Painting a list item is more than merely drawing the text, however. There are a number of flags of the DrawItemState enumeration (accessible through DrawItemEventArgs.State) that must influence the drawing code:

  • Selected – The mouse is hovering over the item. Under normal circumstances, the background will be painted with SystemBrushes.Highlightand the text will be painted in SystemColors.HighlightText.
  • Focus – The item has focus, which usually occurs in conjunction with being selected. A focus rectangle will need to be drawn around it, unless NoAccelerator is set. For new installations of Windows, accelerators are disabled by default.
  • ComboBoxEdit – When the ComboBox has its DropDownStyle set to DropDownList, the presence of this flag means that the item is being painted in the main part of the control. Different rules apply here:
    • When the control is in focus and the drop-down list is not showing, the item should appear as if it were highlighted.
    • When the control is not in focus, or if in focus but dropped down, the item should appear as if it were normal (undecorated).
    • In the case of the grouped combo box, we will not indent items or paint headers for groups in this state.
  • Disabled – This is found in conjunction with ComboBoxEdit, and indicates that the control’s Enabled property is set to false, therefore the text should be rendered using SystemColors.GrayText.

We have some additional stylistic requirements in this case, such as indenting all items with a non-empty group-by value and giving the impression that the group headers are not selectable (by suppressing the highlight and focus rectangle from them). We also want to render the headings in bold, to differentiate them further from the item text.

Example Usage

The following examples demonstrate how fundamentally different types of data sources can be used to produce the same output:

ArrayList of Anonymous Objects

GroupedComboBox groupedCombo = new GroupedComboBox();

groupedCombo.ValueMember = "Value";
groupedCombo.DisplayMember = "Display";
groupedCombo.GroupMember = "Group";

groupedCombo.DataSource = new ArrayList(new object[] {
    new { Value=100, Display="Apples", Group="Fruit" },
    new { Value=101, Display="Pears", Group="Fruit" },
    new { Value=102, Display="Carrots", Group="Vegetables" },
    new { Value=103, Display="Beef", Group="Meat" },
    new { Value=104, Display="Cucumbers", Group="Vegetables" },
    new { Value=0, Display="(other)", Group=String.Empty },
    new { Value=105, Display="Chillies", Group="Vegetables" },
    new { Value=106, Display="Strawberries", Group="Fruit" }
});

DataView

DataTable dt = new DataTable();
dt.Columns.Add("Value", typeof(int));
dt.Columns.Add("Display");
dt.Columns.Add("Group");

dt.Rows.Add(100, "Apples", "Fruit");
dt.Rows.Add(101, "Pears", "Fruit");
dt.Rows.Add(102, "Carrots", "Vegetables");
dt.Rows.Add(103, "Beef", "Meat");
dt.Rows.Add(104, "Cucumbers", "Vegetables");
dt.Rows.Add(DBNull.Value, "(other)", DBNull.Value);
dt.Rows.Add(105, "Chillies", "Vegetables");
dt.Rows.Add(106, "Strawberries", "Fruit");

groupedCombo.DataSource = dt.DefaultView;

String Array (grouped by word length)

groupedCombo.ValueMember = null;
groupedCombo.DisplayMember = null;
groupedCombo.GroupMember = "Length";

string[] strings = new string[] { "Word", "Ace", "Book", "Dice", "Taste", "Two" };

groupedCombo.DataSource = strings;

Final Words

This custom control is an example in enhancing GUI usability and correctly using both the Windows Forms data binding infrastructure and visual style guidelines. With the proper knowledge of how these features operate under-the-hood, I was able to create a component that recycles as much existing functionality as possible, is simple and easy to use in code and avoids breaks with convention that usually result in complex, clunky solutions. Of course, it does not give the ComboBox control a new, multi-level hierarchical structure, merely a categorisation mechanism – however, in this capacity, I hope you find it useful!

Download

Please visit the project page here: Drop-Down Controls

66 thoughts on “A ComboBox Control With Grouping

  1. Pingback: C#/Windows Forms: How to get DrawMode to become DrawMode.OwnerDrawVariable | trouble86.com

    • +1
      vote

      Unfortunately, the CheckedListBox control does not support owner-drawing. You might be better off using a TreeView control with the CheckBoxes option enabled.

      Reply
  2. Pingback: How to get DrawMode to become DrawMode.OwnerDrawVariable

  3. Pingback: Drop-Down Controls Revisited | Brad Smith's Coding Blog

  4. vote

    Thanks a lot for the nice control. I used in my windows form app. it generally works great. But I got some issue:

    I shared this control between two views depending on the user selected view. then i dynamically bind the corresponding data source, let’s say, one view have 4 items, the other one has 6. If i start from 4 items view, then switch to 6 items view, the control works perfectly; but if i start from 6 item view (if i select the item 5 or 6), then switch to 4 items view, then, the control works but with a display issue, now it will not just display the selected value, but it displays something like this { Value=5, Display=”New Photo”, Group=”03-Add Record” }, which is the raw arraylist item.

    so in short, the issue is if i switch from shorter list to a longer list, it works fine with no issue, but if i switch from longer list to shorter list, it works, but got the display issue.

    Could you please see if you can direct me on the possible fix for this issue.

    Thanks a lot.

    Regards.

    Reply
    • -1
      vote

      added the above to your constructor GroupedComboBox fixed my issue

      public GroupedComboBox() {
      base.DropDownStyle = ComboBoxStyle.DropDownList; ////// added this line fixed my issue
      base.DrawMode = DrawMode.OwnerDrawVariable;
      mGroupMember = String.Empty;
      mInternalItems = new ArrayList();
      mTextFormatFlags = TextFormatFlags.EndEllipsis | TextFormatFlags.NoPrefix | TextFormatFlags.SingleLine | TextFormatFlags.VerticalCenter;
      }

      Reply
        • vote

          I found a solution .. You must first set the datasource and then set the valuemember displaymember and groupmember .. that works for me..

          groupedComboBox1.DataSource = new ArrayList(carreras);
          groupedComboBox1.ValueMember = “Value”;
          groupedComboBox1.DisplayMember = “Display”;
          groupedComboBox1.GroupMember = “Group”;

          Reply
  5. +1
    vote

    I was curious if it is possible to make the Group items selectable?

    Here is my ideal solution:

    class GCBItem
    {
    public String Display { get; set; }
    public String Group { get; set; }
    public bool IsGroup { get; set; }
    public object Value { get; set; }
    }

    gcb.Items.Add(new GCBItem(“Group 1”, “G1”, true, “G1”));
    gcb.Items.Add(new GCBItem(“Item 1”, “G1”, false, “G1_I1”));
    gcb.Items.Add(new GCBItem(“Item 2”, “G1”, false, “G1_I2”));

    gcb.Items.Add(new GCBItem(“Group 2”, “G2”, true, “G2”));
    gcb.Items.Add(new GCBItem(“Item 1”, “G2”, false, “G2_I1”));
    gcb.Items.Add(new GCBItem(“Item 2”, “G2”, false, “G2_I2”));

    gcb.DisplayMember = “Display”;
    gcb.GroupMember = “Group”;
    gcb.IsGroupMember = “IsGroup”;
    gcb.ValueMember = “Value”;
    gcb.DataSource = gcb.Items;

    ^ Basically, I would like the current logic as you have it, but if you set a .IsGroupMember value, it uses that to determine if an item is a group item, and what items should go into it, plus it allows the user to select the “Group 1” item.

    Reply
  6. +1
    vote

    Just one thing where I was ripping my hair out:

    When you set the datasource property with your data and then set it again with a different data source a second time, this does not refresh the items in the list. Instead you have to first set the datasource to null and then to the desired list.

    e.g.

    this.cmb_groupedItems.DataSource = null;
    this.cmb_groupedItems.DataSource = myItems;

    Thanks!

    Reply
    • -2
      vote

      Thanks for spotting this – i’ll soon be releasing an updated version that does not suffer from this glitch.

      Reply
  7. vote

    Problem!

    The example code is not run!!. The next code not display elements in combo Box:

    GroupedComboBox groupedCombo = new GroupedComboBox();

    groupedCombo.ValueMember = “Value”;
    groupedCombo.DisplayMember = “Display”;
    groupedCombo.GroupMember = “Group”;

    groupedCombo.DataSource = new ArrayList(new object[] {
    new { Value=100, Display=”Apples”, Group=”Fruit” },
    new { Value=101, Display=”Pears”, Group=”Fruit” },
    new { Value=102, Display=”Carrots”, Group=”Vegetables” },
    new { Value=103, Display=”Beef”, Group=”Meat” },
    new { Value=104, Display=”Cucumbers”, Group=”Vegetables” },
    new { Value=0, Display=”(other)”, Group=String.Empty },
    new { Value=105, Display=”Chillies”, Group=”Vegetables” },
    new { Value=106, Display=”Strawberries”, Group=”Fruit” }
    });

    Why occurs this to me?.

    Thanks for you response.

    Reply
      • vote

        Yes, I have included the control to form (your name is .group_combo_box_produt) !, this control appear in it correctly but your contain is empty.

        When I insert items to combo Box I write:

        this.group_combo_box_produts = new GroupedComboBox();

        Next I test the source code that You write in this page, for example:

        group_combo_box_produts.ValueMember = null;
        group_combo_box_produts.DisplayMember = null;
        group_combo_box_produts.GroupMember = “Length”;

        string[] strings = new string[] { “Word”, “Ace”, “Book”, “Dice”, “Taste”, “Two” };

        group_combo_box_produts.DataSource = strings;

        My comboBox is empty!!!!!!. Not load items!.

        If I execute another example nothing appear.

        Thanks for you response.

        Note: In the page “updated code” not conains a working example application.

        Reply
        • +1
          vote

          It looks like you’re re-instantiating the control unnecessarily. Try removing the line:
          this.group_combo_box_produts = new GroupedComboBox();
          and see if that resolves the problem.

          Reply
          • +2
            vote

            Ok!! you were right, the problem is solved. Thank you.

            You are a master!!!!!!

    • +1
      vote

      I would encourage you to read the original article for more information and examples. The main difference is that the ComboTreeBox uses nodes as its data representation and does not support data-binding.

      Reply
  8. vote

    Hi,
    Can you please tell me how to reverse the order of grouping member elements? The grouping member elements are in alphabetical order by default.

    Reply
    • +1
      vote

      In the Compare(x,y) method, multiply the value of ‘firstLevelSort’ by -1 before the method returns; that will reverse the order of the grouping elements but retain the order of the child elements. If you wanted to be able to control this behaviour dynamically, you could add a SortDirection (ascending/descending) property to the control – which would multiply the value by either +1 or -1.

      Reply
      • vote

        Hello,
        thank you for the great solution.
        1. How could i modify the sorting to insert an item like “Create…” on index 0 before the first group?
        2. How can i remove the empty space before the items

        Reply
        • vote

          Thanks for your support!

          Re: Forcing an item to always be at index 0, there are a couple of ways to achieve this, but I think this is the simplest way:
          Firstly, set the SortMode property to GroupItemSortModes.Item – this passes the entire combo box item to the sort comparer.
          Then, create a custom IComparer implementation and set the SortComparer property.
          In your comparer class, you will need to test for some condition that is true for the “Create…” item only. In the example below, I assign ‘SOME_VALUE’ to the Value property of that item:

          public int Compare(object x, object y) {
              dynamic dx = x; // or cast to a specific type
              dynamic dy = y;
          
              if ((x is string) || (y is string))
                  return Comparer.Default.Compare(x, y); // this deals with group headings
              else if (dx.Value == SOME_VALUE)
                  return -1;
              else if (dy.Value == SOME_VALUE)
                  return 1;
              else
                  return Comparer.Default.Compare(dx.Display, dy.Display); // this deals with every other item
          }

          I’m not sure what you mean by empty space before the items – do you mean the left indent? If so, this is hard-coded into the control’s Paint method. You would have to override OnDrawItem in order to change this.

          Reply
          • vote

            Hello,
            thanks for your answer.
            I have to extend my question. I want to insert an item like “Create / Edit…” at the top or at the end of the comboBox. You can see it often in dropdowns on a website form.
            What i’ve tried to have it at the top:
            I used a “Group” value like “zzz” to have it on index 0 and tried to remove the header (Groupname) by defining “e.ItemHeight” in the “OnMearsureItem” method. Unfortunately it doesn’t work properly. The item should be above the first group.

            – I’ve realized that i have to deactivate the group header sorting because i wants to translate my application to a few languages for usability.
            – I also wants to deactivate the sorting in the first group after the “Create” item because it should have predefined settings.
            Example:
            “Create” item [first group] -> Predefined Settings [2nd group] -> Userdefined Settings [3rd group]

            Yes, i guess the left indent is defined in the “OnDrawItem” method by itemBounds.

            – I found a very elegant dropdown on the web with lines between the groups. I think this would be also a nice extension worth considering for the combobox.
            (Example: https://www.jotform.com/widgets/grouped-values-dropdown)

  9. +1
    vote

    Was trying to use this control as a custom comboboxcell column in a datagridview, although it sort of works the grouping doesn’t. I don’t mind fixing, however, could you give me some pointers as to why this might be the case?

    Reply
    • +1
      vote

      Two key things are needed in order to get the grouping behaviour to work;
      1) DrawMode must be set to OwnerDrawVariable. Make sure the DataGridView isn’t changing this property on the editing control.
      2) The GroupMember property must be set on the editing control. If you’ve added an equivalent property to the cell/column, be sure to apply its value to the editing control. You can do this in the DataGridViewCell.InitializeEditingControl method (of the cell) or the IDataGridViewEditingControl.PrepareEditingControlForEdit (of the editing control).

      Reply
  10. vote

    Hi,first thanks for your good control,it’s great
    i have a question,how can i change the COLOR of subItem in group of ComboBox?
    thanks for your help
    with best regards

    Reply
    • vote

      In this implementation, the colour for all of the drop-down items in the GroupedComboBox is determined by the control’s ForeColor property. If you want to be able to set the colour of each item individually, you would have to add an additional databinding property to the control (using GroupMember as an example), and add an appropriate member to the items in the data source that contains the colour you want to use. This would be a highly-specialised solution, however, and at this point I would suggest using a more appropriate type of control to display your items. This sort of requirement goes beyond what databinding in Windows Forms was designed for.

      You may want to try a control with a hierarchical data model, such as my ComboTreeBox. It would be much easier to implement individual node colours using this control than the GroupedComboBox.

      Reply
      • vote

        Hi,

        And thanks for this wonderful example, looks pretty good. I wanted to ask you if you had done the same kind of work for a listbox or listview instead?

        Angelo

        Reply
        • +1
          vote

          These principles can be applied to the ListBox control very easily, as they use the same mechanism of owner-drawing as the ComboBox. I might adapt my code at some point in the future.

          As for the ListView control, it already has a grouping mechanism (albeit a cumbersome one). Due to the lack of support for data-binding, this control is not ideal.

          Reply
          • vote

            I may well give it a whirl and see if I can simply adapt the code to the listbox, if they use the events then I suoppose I could clone the class you created and simply switch from combobox/dropdownlist to listbox …

  11. Pingback: DataGridView Column Types for Drop-Down Controls | Brad Smith's Coding Blog

    • vote

      Since the GroupedComboBox control extends the existing ComboBox control, you can achieve this by simply setting the AutoCompleteMode property to Suggest (or Append, or SuggestAppend as desired) and the AutoCompleteSource property to ListItems. Only the items themselves will appear in the auto-complete suggestions; the groups will only appear in the main drop-down.

      Reply
  12. vote

    Hi Brad,

    Thank you so much for this!

    Is there a way to disable the combobox from auto-selecting an item in the dropdown until I click on one?

    Thank you

    Reply
    • vote

      Hi Luke, this is the default behaviour for all ComboBox controls that use data-binding (on Windows Vista and later). The easiest way to get around this is to set the SelectedItem property to null immediately after setting the data source (or adding items).

      Reply
  13. vote

    Hi,
    thank you for your control.
    Did you know or do you have a solution why no list is shown when I only have one Item?
    For example when you do this:

    groupedCombo.ValueMember = null;
    groupedCombo.DisplayMember = null;
    groupedCombo.GroupMember = “Length”;
    string[] strings = new string[] { “Word” };
    groupedCombo.DataSource = strings;

    No list will open when you click the drop down button???

    Regards
    Martin

    Reply
    • vote

      Hi Martin, I can reproduce your problem but, at this stage, I cannot explain why it is happening. The control is returning the correct item height in the MeasureItem event, which should result in a dropdown with a height equal to twice the font size. It’s possible that there could be a bug in the ComboBox control when in OwnerDrawVariable mode. I will have to test this more extensively.

      Reply
      • vote

        Thank You for your reply, yes I also was looking into the code and was looking into the function OnMeasureItem and can’t find any failure. Thats the reason why I asked you 🙂
        Thank You if You will test more in this failure.

        Regards
        Martin

        Reply
      • vote

        Hi,
        its me again 🙂
        I test a little bit and found a solution.
        If you set groupedCombo.ItemHeight = Font.Height * 2 it is working but than the hole combobox has this height.

        If you set
        groupedCombo.IntegralHeight = False;
        groupedCombo.DropDownHeight = 200;

        Than it is working. Maybe this helps others 😉

        Thank You!

        Reply
  14. vote

    Hi,
    In this dropdown i think it is automatically sorting the items…How can i avoid it and sort from my query…. Is it possible?

    Reply
    • +1
      vote

      Yes, it is necessary for the control to sort the items using custom logic because that is how the grouping is achieved. To change this behaviour you would have to modify the IComparer.Compare method and replace the default comparer (for ‘secondLevelSort’) with your own implementation.

      Reply
  15. vote

    Hello , first congratulations on the component.
    I use vb.net and am trying to use your component.
    My syntax to populate a combobox component is :


    Dim Conexao As New OleDb.OleDbConnection(CONNECTION_STRING)
    Dim ConsultaSQL As String = ""
    Conexao.Open()
    Dim SQLComando As New OleDb.OleDbCommand(ConsultaSQL, Conexao)
    Dim Ler As OleDbDataReader
    Dim SQLTexto As String = ""

    If Texto.Trim "" Then
    SQLTexto = " WHERE uCase(titulo) like '%" & Texto.ToUpper & "%' or uCase(localizacao) like '%" & Texto.ToUpper & "%' or endereco like '%" & Texto & "%' "
    End If

    ConsultaSQL = "SELECT * FROM equipamentos " & SQLTexto & " ORDER BY grupo ASC,titulo ASC"
    Dim SQLCommand As New OleDb.OleDbCommand(ConsultaSQL, Conexao)
    Ler = SQLCommand.ExecuteReader()

    cmbTextoPesquisa.Items.Clear()
    cmbTextoPesquisa.Items.Add(New ComboTagLista("", ""))

    Dim nCont As Integer = 0
    Dim GrupoAux As String = ""

    Dim Dispositivos As New AutoCompleteStringCollection

    While Ler.Read
    Dim ID As String = Ler.Item("ID").ToString
    Dim Titulo As String = Ler.Item("Titulo").ToString
    Dim Tipo As String = Ler.Item("tipo").ToString
    Dim IDGrupo As String = Ler.Item("grupo").ToString
    Dim Grupo As String = NomeGrupo(Ler.Item("grupo").ToString)

    If GrupoAux Grupo Then
    cmbTextoPesquisa.Items.Add(New ComboTagLista("_____________________________________", ""))
    cmbTextoPesquisa.Items.Add(New ComboTagLista("", "CONEXOES#" & IDGrupo))
    GrupoAux = Grupo
    End If

    cmbTextoPesquisa.Items.Add(New ComboTagLista(Titulo, Tipo.ToUpper & "#" & ID))
    Dispositivos.Add(Titulo)
    nCont += 1
    End While

    cmbTextoPesquisa.AutoCompleteMode = AutoCompleteMode.Suggest
    cmbTextoPesquisa.AutoCompleteSource = AutoCompleteSource.CustomSource
    cmbTextoPesquisa.AutoCompleteCustomSource = Dispositivos

    Ler.Close()
    Conexao.Close()

    But I can not adapt to your component , any suggestions?

    Reply
    • vote

      Hi, you will not get the grouping functionality if you add items directly to the Items property of the control; you must use the DataSource property and set the DisplayMember, ValueMember and GroupMember properties accordingly. Also, grouping will not appear in the autocomplete popup.

      Reply
    • vote

      Hi, I hope you will read my message, this post is 2 years old…

      btw, I would like to use the GroupedComboBox.cs in one of my vb.net projects.

      How can use it?

      Do I need to translate GroupedComboBox.cs to VB ?

      Thanks in advance

      Reply
      • vote

        Here again, just sort…I didn’t see the DLL …In order to use this great combo in vb.net, I think the only way is using the DLL. Isn’t it?

        Now I have my combo box with groups…thanks to the author for sharing his good control.

        Reply
        • vote

          Yes, that’s correct. There’s no need to port the code to VB.NET, you can just use the pre-built assembly.

          Reply
  16. vote

    Ok!
    Thanks for the feedback, I will do more tests using DataSource property and set the DisplayMember, ValueMember and GroupMember properties.

    And congratulations on component development.

    Reply
    • vote

      Licence information can be found here as well as in the LICENSE.TXT file included in the source and binary distributions.

      Reply
  17. vote

    hi thanks for this component. the grouComboBox is exactly what i needed. it it noticeably sluggish compared to a combobox with about 30 items in the dataset though. is there anything i can do to improve this? as long as the items are grouped correctly i do not care about order.

    Reply
    • vote

      I can’t say that i’ve observed any sluggishness under normal operation, even with 100+ items in the dropdown. Is the data source changing often? The control rebuilds its internal list of items whenever a change to the data source is detected, so this could account for the behaviour you described. Unfortunately, the items must be sorted in order for the grouping to be performed correctly so this is a necessary overhead. I’m sure it could be optimised for a specific application, but the data source should not really be changing frequently enough to cause a noticeable slowdown.

      Another possible explanation is that the property referenced by GroupMember does not exist or is throwing exceptions (although this would mean that the grouping was already failing).

      Reply
    • vote

      At minimum, the collection must be sorted by group. By default, the control sorts first by group and then by item, using the default comparer. If you can guarantee that the items in the collection are already sorted by group, you can write your own IComparer implementation and assign it to the SortComparer property of the control. Rather than comparing the items, it can simply compare their indices.

      Reply

Leave a reply

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> 

required