15

I'm trying to modify the behaviour of a Windows.Forms ComboBox so that the AutoComplete drop down displays items according to the rules I specify.

By default, if you use AutoComplete in a ComboBox, the rule that's followed is "string s is included in the drop down if( s.StartsWith( userEnteredTextInTheComboBox) )" All I'm really interested in is substituting a new rule for the current one, but I can find no way to get at it. (Specifically, I'd prefer s.Contains instead of s.StartsWith.)

I can kludge together a clumsy solution using two controls instead of one, but I'd really be happier with one that actually does what I want.

Update: I found essentially the same question after some more searching. The answer supplied there suggests that using two controls to "fake it" is the way to go.

Community
  • 1
  • 1
user272551
  • 151
  • 1
  • 1
  • 5

3 Answers3

20

I've had the same problem and looked for a quick solution.

Eventually I ended up writing it myself. It's a little dirty but it should not be hard to make it prettier if needed.

The idea is to re-build the combo list after every key press. This way we can rely on the combo's built-in interface, and we don't need to implement our own interface with a textbox and a listbox...

Just remember to set combo.Tag to null if you re-build the combo's options list.

private void combo_KeyPress(object sender, KeyPressEventArgs e) {
    comboKeyPressed();
}

private void combo_TextChanged(object sender, EventArgs e) {
    if (combo.Text.Length == 0) comboKeyPressed();
}

private void comboKeyPressed() {
    combo.DroppedDown = true;

    object[] originalList = (object[])combo.Tag;
    if (originalList == null) {
        // backup original list
        originalList = new object[combo.Items.Count];
        combo.Items.CopyTo(originalList, 0);
        combo.Tag = originalList;
    }

    // prepare list of matching items
    string s = combo.Text.ToLower();
    IEnumerable<object> newList = originalList;
    if (s.Length > 0) {
        newList = originalList.Where(item => item.ToString().ToLower().Contains(s));
    }

    // clear list (loop through it, otherwise the cursor would move to the beginning of the textbox...)
    while (combo.Items.Count > 0) {
        combo.Items.RemoveAt(0);
    }

    // re-set list
    combo.Items.AddRange(newList.ToArray());
}
obe
  • 7,378
  • 5
  • 31
  • 40
  • I did it but I should move this line "combo.DroppedDown = true;" in order to be the last line of the function. Otherwise it throws an exception when trying to remove the last item "combo.Items.RemoveAt(0);" – Ehsan Feb 28 '17 at 13:07
  • I receive an exception complaining about item cannot be removed when the data source property is set on the combo – kuklei Apr 05 '20 at 23:29
  • @kuklei this cannot be done with a databound combo; populate items yourself manually like the answer does – Caius Jard Dec 02 '21 at 08:38
1

Before Windows Vista, the Autocomplete object match candidates with prefix only, so you need to cook your own.

If you need to reset the suggestion list when it is visible, use IAutoCompleteDropDown::ResetEnumerator.

Sheng Jiang 蒋晟
  • 15,125
  • 2
  • 28
  • 46
0

Thanks to Ehsan. Just for reference. I ended up with this.

    private void comboBoxIdentification_TextChanged(object sender, EventArgs e)
    {
        if (comboBoxIdentification.Text.Length == 0)
        {
            comboBoxIdentificationKeyPressed(comboBoxIdentification, comboBoxIdentification.Text);
        }
    }

    private void comboBoxIdentificationKeyPressed(ComboBox comboBoxParm, string text )
    {
        comboBoxParm.DroppedDown = true;
        object[] originalList = (object[])comboBoxParm.Tag;
        if (originalList == null)
        {
            // backup original list
            originalList = new object[comboBoxParm.Items.Count];
            comboBoxParm.Items.CopyTo(originalList, 0);
            comboBoxParm.Tag = originalList;
        }

        // prepare list of matching items
        string s = text.ToLower();
        IEnumerable<object> newList = originalList;
        if (s.Length > 0)
        {
            newList = originalList.Where(item => item.ToString().ToLower().Contains(s));
        }

        // clear list (loop through it, otherwise the cursor would move to the beginning of the textbox...)
        while (comboBoxParm.Items.Count > 0)
        {
            comboBoxParm.Items.RemoveAt(0);
        }
        var newListArr = newList.ToArray();
        // re-set list
        comboBoxParm.Items.AddRange(newListArr);
    }
lbolanos
  • 21
  • 1
  • 2