How can I dynamically change auto complete entries in a C# combobox or textbox?

C#WinformsAutocompleteComboboxTextbox

C# Problem Overview


I have a combobox in C# and I want to use auto complete suggestions with it, however I want to be able to change the auto complete entries as the user types, because the possible valid entries are far too numerous to populate the AutoCompleteStringCollection at startup.

As an example, suppose I'm letting the user type in a name. I have a list of possible first names ("Joe", "John") and a list of surnames ("Bloggs", "Smith"), but if I have a thousand of each, then that would be a million possible strings - too many to put in the auto complete entries. So initially I want to have just the first names as suggestions ("Joe", "John") , and then once the user has typed the first name, ("Joe"), I want to remove the existing auto complete entries and replace them with a new set consisting of the chosen first name followed by the possible surnames ("Joe Bloggs", "Joe Smith"). In order to do this, I tried the following code:

void InitializeComboBox()
{
    ComboName.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
    ComboName.AutoCompleteSource = AutoCompleteSource.CustomSource;
    ComboName.AutoCompleteCustomSource = new AutoCompleteStringCollection();
    ComboName.TextChanged += new EventHandler( ComboName_TextChanged );
}

void ComboName_TextChanged( object sender, EventArgs e )
{
    string text = this.ComboName.Text;
    string[] suggestions = GetNameSuggestions( text );

    this.ComboQuery.AutoCompleteCustomSource.Clear();
    this.ComboQuery.AutoCompleteCustomSource.AddRange( suggestions );
}

However, this does not work properly. It seems that the call to Clear() causes the auto complete mechanism to "turn off" until the next character appears in the combo box, but of course when the next character appears the above code calls Clear() again, so the user never actually sees the auto complete functionality. It also causes the entire contents of the combo box to become selected, so between every keypress you have to deselect the existing text, which makes it unusable. If I remove the call to Clear() then the auto complete works, but it seems that then the AddRange() call has no effect, because the new suggestions that I add do not appear in the auto complete dropdown.

I have been searching for a solution to this, and seen various things suggested, but I cannot get any of them to work - either the auto complete functionality appears disabled, or new strings do not appear. Here is a list of things I have tried:

  • Calling BeginUpdate() before changing the strings and EndUpdate() afterward.
  • Calling Remove() on all the existing strings instead of Clear().
  • Clearing the text from the combobox while I update the strings, and adding it back afterward.
  • Setting the AutoCompleteMode to "None" while I change the strings, and setting it back to "SuggestAppend" afterwards.
  • Hooking the TextUpdate or KeyPress event instead of TextChanged.
  • Replacing the existing AutoCompleteCustomSource with a new AutoCompleteStringCollection each time.

None of these helped, even in various combinations. Spence suggested that I try overriding the ComboBox function that gets the list of strings to use in auto complete. Using a reflector I found a couple of methods in the ComboBox class that look promising - GetStringsForAutoComplete() and SetAutoComplete(), but they are both private so I can't access them from a derived class. I couldn't take that any further.

I tried replacing the ComboBox with a TextBox, because the auto complete interface is the same, and I found that the behaviour is slightly different. With the TextBox it appears to work better, in that the Append part of the auto complete works properly, but the Suggest part doesn't - the suggestion box briefly flashes to life but then immediately disappears.

So I thought "Okay, I'll live without the Suggest functionality and just use Append instead", however when I set the AutoCompleteMode to Append, I get an access violation exception. The same thing happens with Suggest - the only mode that doesn't throw exceptions is SuggestAppend, even though the Suggest part doesn't then behave correctly.

I thought that it was supposed to be impossible to get access violation exceptions when using C# managed code. Avram suggested I use "lock" to fix this, but I don't know what I should lock - the only thing that has a SyncRoot member is the AutoCompleteStringCollection, and locking that doesn't prevent the access violation exceptions. I also tried locking the ComboBox or TextBox, but that didn't help either. As I understand it, lock only prevents other locks, so if the underlying code isn't using lock then my using it won't make any difference.

The upshot of all this is that I can't currently use a TextBox or a ComboBox with dynamic auto complete. Does anyone have any insights into how I could achieve this?

Update:

I still haven't got this working, but I have found out some more. Maybe some of this will inspire someone else to come up with a solution.

I tried replacing the ComboBox with a TextBox, because the auto complete interface is the same, and I found that the behaviour is slightly different. With the TextBox it appears to work better, in that the Append part of the auto complete works properly, but the Suggest part doesn't - the suggestion box briefly flashes to life but then immediately disappears.

So I thought "Okay, I'll live without the Suggest functionality and just use Append instead," however when I set the AutoCompleteMode to Append, I get an access violation exception. The same thing happens with Suggest - the only mode that doesn't throw exceptions is SuggestAppend, even though the Suggest part doesn't then behave correctly.

I thought that it was supposed to be impossible to get access violation exceptions when using C# managed code, but anyway, the upshot is that I can't currently use a TextBox or a ComboBox with any kind of dynamic auto complete. Does anyone have any insights into how I could achieve this?

Update 2:

After trying various other things such as changing the autocomplete in a worker thread, and using BeginInvoke() to simulate PostMessage() type behaviour, I finally gave up and just implemented my own auto complete dropdown using a list box. It's much more responsive than the built-in one, and I spent less time doing that than I did trying to get the built-in one to work, so the lesson for anyone else who wants this behaviour is - you're probably better off implementing it yourself.

C# Solutions


Solution 1 - C#

I had the same problem, and found an extremely simple workaround. As everybody else here, I couldn't find any means to control de behaviour of the component, so I had to accept it.

The natural behaviour is: you can't dynamically populate the list every time the user types into the text box. You have to populate it once, and then the AutoComplete mechanism takes control. The conclusion is: you should populate the AutoCompleteCustomSource with every possible entry in you database to make it work as we want.

Of course this is not viable if you have millions of records to populate the list. Performance issues in data transfer and the AutoComplete mechanism itself will not allow you to do that.

The compromise solution I found was: dynamically populate the AutoCompleteCustomSource every time that the Text length reaches exactly N chars (3 in my case). This worked because complexity was drastically reduced. The number of records that are fetched from the database that match these 3 initial chars was small enough to avoid any performance issues.

The major drawback is: users will not be presented the AutoComplete list until they type the N-th char. But it seems like users don't really expect a meaningful AutoComplete list before 3 chars are typed.

Hope this helps.

Solution 2 - C#

This worked for me, you don't addRange to the same AutoCompleteStringCollection, but rather create a new one each time.

form.fileComboBox.TextChanged += (sender, e) => {
	var autoComplete = new AutoCompleteStringCollection();
	string[] items = CustomUtil.GetFileNames();
	autoComplete.AddRange(items);
	form.fileComboBox.AutoCompleteCustomSource = autoComplete;
};

Solution 3 - C#

I think you might want to get out the reflector and look at overriding the autocomplete behaviour in the combobox itself. I'm certain the autocompletion would call a function which accesses the autocomplete list. If you can find this function and override it, you can use any behaviour you want.

See what documentation you can find on the combobox class itself.

Solution 4 - C#

I haven't test this, but it may be worth a shot.

Instead of clearing the AutoCompleteCustomSource, double buffer by keeping two instances. When the text changes, call GetNameSuggestions() and build the strings for the one that's not currently being used, then set ComboName.AutoCompleteCustomSource to the one you just set up.

I think it should look something like this.

AutoCompleteCustomSource accs_a;
AutoCompleteCustomSource accs_b;
bool accs_check = true; //true for accs_a, false for accs_b
void InitializeComboBox()
{
    ComboName.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
    ComboName.AutoCompleteSource = AutoCompleteSource.CustomSource;
    
    accs_a = new AutoCompleteStringCollection();
    accs_b = new AutoCompleteStringCollection();

    ComboName.AutoCompleteCustomSource = accs_a;
    ComboName.TextChanged += new EventHandler( ComboName_TextChanged );
}

void ComboName_TextChanged( object sender, EventArgs e )
{
    string text = this.ComboName.Text;

    if(accs_check)
    {
       accs_b.Clear();
       accs_b.AddRange(GetNameSuggestions( text ));
       accs_check = false;
    }
    else
    {
       accs_a.Clear();
       accs_a.AddRange(GetNameSuggestions( text ));
       accs_check = true;
    }

    this.ComboQuery.AutoCompleteCustomSource = accs_check? accs_a : accs_b;
}

Solution 5 - C#

update: main reason to put the lock on this place is

its working :) most of "mysterious exception" that i ever have, after this trick disappear


  1. the lock like in this code, can help with your exception
  2. as you mention before, there is less problem with using textbox
  3. in this code, SuggestAppend working fine


    private void Form1_Load(object sender, EventArgs e)
    {
        textBox1.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
        textBox1.AutoCompleteSource = AutoCompleteSource.CustomSource;

        textBox1.TextChanged+=new EventHandler(textBox1_TextChanged);

        col1.AddRange(new string[] { "avi avi", "avram avram" });
        col2.AddRange(new string[] { "boria boria", "boris boris" });

        textBox1.AutoCompleteCustomSource = col1;
        textBox1.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
    }
    AutoCompleteStringCollection col1 = new AutoCompleteStringCollection();
    AutoCompleteStringCollection col2 = new AutoCompleteStringCollection();

    object locker = new object();
    private void textBox1_TextChanged(object sender, EventArgs e)
    {
        lock (locker)
        {
            if (textBox1.Text.StartsWith("a") && textBox1.AutoCompleteCustomSource != col1)
            {
                textBox1.AutoCompleteCustomSource = col1;
            }
            if (textBox1.Text.StartsWith("b") && textBox1.AutoCompleteCustomSource != col2)
            {
                textBox1.AutoCompleteCustomSource = col2;
            }
        }
    }

Solution 6 - C#

if(!textBox3.AutoCompleteCustomSource.Contains(textBox3.Text))
   textBox3.AutoCompleteCustomSource.Add(textBox3.Text);

Solution 7 - C#

Sam, have you got this figured out? I am running into the same situation. Clear() seems to cause the exception. I removed the call to clear and I'm getting the correct suggestions event though the collection keeps on growing...

Also, regarding the private members: you can access them using reflection:

PropertyInfo[] props = [object].GetType().GetProperties({flags go here});
props[0].SetValue(this, new object[] { 0 });

Solution 8 - C#

I came here initially looking for a solution, but have now found my own.

The trick is not to call Clear() on the AutoCompleteCustomSource but to remove all items in a for loop and then rebuild the list with the new data. In my case (a book collection application) I'm retrieving author names from a database with a specific starting letter, instead of the whole lot. Note that this will only work if the textbox part of the combobox is or has become empty.

    private void cboAuthor_KeyDown(object sender, KeyEventArgs e)
    {
        if (cboAuthor.Text.Length == 0)
        {
            // Next two lines simple load data from the database in the
            // into a collection (var gateway), base on first letter in
            // the combobox. This is specific to my app.
            var gateway = new AuthorTableGateway();
            gateway.LoadByFirstLetter(Char.ConvertFromUtf32(e.KeyValue)[0]);

            // Clear current source without calling Clear()
            for (int i = 0; i < authorsAutoComplete.Count; i++)
                authorsAutoComplete.RemoveAt(0);

            // Rebuild with new data
            foreach (var author in gateway)
                authorsAutoComplete.Add(author.AuthorName);
        }
    }

Solution 9 - C#

Haven't try this, but for your specific case you could code something like:

    private void txtAutoComplete_KeyUp(object sender, KeyEventArgs e)
    {

        String text = txtAutoComplete.Text;

        if (text.EndsWith(" "))
        {

            string[] suggestions = GetNameSuggestions( text ); //put [text + " "] at the begin of each array element
            txtAutoComplete.AutoCompleteCustomSource.Clear();
            txtAutoComplete.AutoCompleteCustomSource.AddRange( suggestions );

        }

    }

Solution 10 - C#

For me the secret was using the TextChanged event and none of the KeyDown/Up/Press, etc.

Update: After having other issues with dynamically changing the AutoCompleteCustomSource I eventually abandoned using the builtin Autocomplete functionality and implemented my own in much shorter time than I had wasted on it originally. There seems to be some issues in unmanaged code that implements the ComboBox control. Specifically, I was having issues with the TextChanged event handler firing when it should. I decided to only use the OnKeyDown/Press/Up handlers in my custom implementation and that seemed to be more reliable.

Solution 11 - C#

The best solution for this is to use combobox's event handlers. By using textUpdate KeyDown DropDown and ChangeCommit, you can imitate autocompletemode and you can customize what to search and what to appear in the drop down.

I found this answer useful but it is coded in visual c++ and it is toolstripcombobox but the concept is identical. Anyway there is a huge similarity of the c# and c++ in .net and it should not be a problem in understanding the solution.

https://stackoverflow.com/questions/27559900/customized-autosearch-of-toolstripcombobox-in-visual-c

Solution 12 - C#

This is a very old problem I know, but it is one which still exists today. My workaround was to set the Autocomplete mode and source properties to 'none' and manually update the items on KeyUp event.

I'm sure it's hacky but it works perfectly for me without issue for quite a while regardless of the speed the data is entered, with the added bonus of my hair starting to grow back.

You can also choose whether to just suggest, or suggest and append. I hope it might help someone.

private void comboBox1_KeyUp(object sender, KeyEventArgs e)
    {

        if (string.IsNullOrWhiteSpace(comboBox1.Text))
        {
            e.Handled = true;
            return;
        }
        if (comboBox1.Text.Length < 3)
        {
            e.Handled = true;
            return;
        }

        if (e.KeyCode == Keys.Down || e.KeyCode == Keys.Up)
        {
            e.Handled = true;
            return;
        }
        else if (e.KeyCode == Keys.Back)
        {
            e.Handled = true;
            return;
        }

        string text = comboBox1.Text;

        if (e.KeyCode == Keys.Enter)
        {
            comboBox1.DroppedDown = false;
            comboBox1.SelectionStart = text.Length;
            e.Handled = true;
            return;
        }

        List<string> LS = Suggestions(comboBox1.Text);

        comboBox1.Items.Clear();
        comboBox1.Items.AddRange(LS.ToArray());

        //If you do not want to Suggest and Append
        //comment the following line to only Suggest
        comboBox1.Focus();

        comboBox1.DroppedDown = true;
        comboBox1.SelectionStart = text.Length;

        //Prevent cursor from getting hidden
        Cursor.Current = Cursors.Default;
        e.Handled = true;
    }

Solution 13 - C#

After trying all the solutions that have been offered here (without success) I found something that works for me:

private void CellBox_TextChanged(object sender, EventArgs e)
{
    ((TextBox)sender).TextChanged -= CellBox_TextChanged;
    ((TextBox)dataGridView1.EditingControl).AutoCompleteMode = AutoCompleteMode.None;
    ((TextBox)dataGridView1.EditingControl).AutoCompleteCustomSource = null;                
    aCSC.Clear();
    foreach (string value in Autocompletevalues())
    {
        aCSC.Add(value);
    }
    ((TextBox)dataGridView1.EditingControl).AutoCompleteCustomSource = aCSC;
    ((TextBox)dataGridView1.EditingControl).AutoCompleteMode = AutoCompleteMode.Suggest;
    ((TextBox)sender).TextChanged += CellBox_TextChanged;
}

Steps:

  • Disable Eventhandler
  • Disable Autocomplete Mode
  • Set Source to null
  • Update AutoCompleteStringCollection (aCSC)
  • Set Source to updated AutoCompleteStringCollection
  • Activate Autocomplete Mode
  • enable Eventhandler

I hope it helps someone..

Solution 14 - C#

use this code

private void dataGridView1_EditingControlShowing(object sender,DataGridViewEditingControlShowingEventArgs e)
    {

        if (e.Control is DataGridViewComboBoxEditingControl)
        {
            ((ComboBox)e.Control).DropDownStyle = ComboBoxStyle.DropDown;
            ((ComboBox)e.Control).AutoCompleteSource = AutoCompleteSource.ListItems;
            ((ComboBox)e.Control).AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.Suggest;
        }

}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSam HopkinsView Question on Stackoverflow
Solution 1 - C#Alexandre MafraView Answer on Stackoverflow
Solution 2 - C#JaanusView Answer on Stackoverflow
Solution 3 - C#SpenceView Answer on Stackoverflow
Solution 4 - C#Adam HaileView Answer on Stackoverflow
Solution 5 - C#AvramView Answer on Stackoverflow
Solution 6 - C#RandyView Answer on Stackoverflow
Solution 7 - C#fixitchrisView Answer on Stackoverflow
Solution 8 - C#Jim CramerView Answer on Stackoverflow
Solution 9 - C#Luca FagioliView Answer on Stackoverflow
Solution 10 - C#BoogView Answer on Stackoverflow
Solution 11 - C#catzillaView Answer on Stackoverflow
Solution 12 - C#TEDSONView Answer on Stackoverflow
Solution 13 - C#steloeView Answer on Stackoverflow
Solution 14 - C#Jahanzaib AhmadView Answer on Stackoverflow