C# Adding Filter to combobox dropdown list

TomTommyTom picture TomTommyTom · Dec 5, 2016 · Viewed 23.2k times · Source

Need some help with adding filter to my ComboBox drop down list(windows Forms Visual studio 2015)

The drop down is populated as per below:

public ReconciliationReport()
{
    InitializeComponent();
    AppDomain.CurrentDomain.AssemblyResolve += FindDLL;

    this.sRootDirectory = Properties.Resources.sRootDirectory;

    string[] arrProjectList = Directory.GetDirectories(sRootDirectory).Select(Directory => Path.GetFileName(Directory)).ToArray();
    Array.Sort(arrProjectList);

    int iProjectCount = arrProjectList.Length;
    this.DropDownListSize = iProjectCount;

    for (int i = 0; i < iProjectCount; i++)
    {
        SelectJobDropdown.Items.Add(arrProjectList[i]);
    }
}

This gives me a nice drop down list of all current directories. enter image description here

Now, I need to add a filer to show only items which contain a text typed into the ComboBoxitself regardless if the dropdown list itself is open or not.

I have disabled both AutoCompleteMode and AutoCompleteSource as it was not working as expected with the opened droped down list. It was opening additonal list on top the existing one but I could only select from the dropdown under it. See print screen below: enter image description here

The list on top is inactive and I cannot select the text but also does not give an option to display substrings.

Only have one even for the box itself which is

private void SelectJobDropdown_SelectedIndexChanged(object sender, EventArgs e) 
{
    //Plenty of code here 
}

Can someone point in the right direction how to filter the list as I type within the box itself.

Please NOTE I have been using C# for only 3 weeks so might get confused with some of the terminology or other aspects of this language etc.

Answer

Mong Zhu picture Mong Zhu · Dec 6, 2016

I would suggest to use 2 Lists. 1 for the original values

List<string> arrProjectList;

public ReconciliationReport()
{
    InitializeComponent();
    AppDomain.CurrentDomain.AssemblyResolve += FindDLL;

    this.sRootDirectory = Properties.Resources.sRootDirectory;

    arrProjectList = Directory.GetDirectories(sRootDirectory).Select(Directory => Path.GetFileName(Directory)).ToList();
    arrProjectList.Sort();

    // then just bind it to the DataSource of the ComboBox
    SelectJobDropdown.DataSource = arrProjectList;
    // don't select automatically the first item
    SelectJobDropdown.SelectedIndex = -1;
}

and 1 for the filtered values. In this example I use a TextBox to catch the filter text. In the TextChanged event take the filter text and pull out only those values from the original arrProjectList List. You would need an extra option at the end to reset the binding to the old list if the filter is empty.

private void textBox1_TextChanged(object sender, EventArgs e)
{
    string filter_param = textBox1.Text;

    List<string> filteredItems = arrProjectList.FindAll(x => x.Contains(filter_param));
    // another variant for filtering using StartsWith:
    // List<string> filteredItems = arrProjectList.FindAll(x => x.StartsWith(filter_param));

    comboBox1.DataSource = filteredItems;

    // if all values removed, bind the original full list again
    if (String.IsNullOrWhiteSpace(textBox1.Text))
    {
        comboBox1.DataSource = arrProjectList;
    }

    // this line will make sure, that the ComboBox opens itself to show the filtered results       
}

EDIT

I found a Solution for typing the filter into the ComboBox directly. The filtering is the same procedure, but using the TextUpdate event it is necessary to unselect the SelectedIndex which is automatically set to the first element after the binding. Then I guess you want to proceed to write your filter (more than just one letter), write the filter back into the ComboBox.Text property and set the cursor position to the end:

private void comboBox1_TextUpdate(object sender, EventArgs e)
{
    string filter_param = comboBox1.Text;

    List<string> filteredItems = arrProjectList.FindAll(x => x.Contains(filter_param));
    // another variant for filtering using StartsWith:
    // List<string> filteredItems = arrProjectList.FindAll(x => x.StartsWith(filter_param));

    comboBox1.DataSource = filteredItems;

    if (String.IsNullOrWhiteSpace(filter_param))
    {
        comboBox1.DataSource = arrProjectList;
    }
    comboBox1.DroppedDown = true;

    // this will ensure that the drop down is as long as the list
    comboBox1.IntegralHeight = true;

    // remove automatically selected first item
    comboBox1.SelectedIndex = -1;

    comboBox1.Text = filter_param;

    // set the position of the cursor
    comboBox1.SelectionStart = filter_param.Length;
    comboBox1.SelectionLength = 0;            
}

Et voilà automatic filtering with a nice display and arrow selection afterwards.

EDIT 2

for case insensitive search you can use this:

List<string> filteredItems = arrProjectList.FindAll(x => x.ToLower().Contains(filter_param.ToLower()));

NOTE:

After the opening of the dropdownlist the cursor will disappear. To prevent this the Cursor.Current has to be set to Cursor.Defualt

comboBox1.DroppedDown = true;
Cursor.Current = Cursors.Default;