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

Sam Hopkins picture Sam Hopkins · Feb 5, 2009 · Viewed 53.3k times · Source

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.

Answer

Alexandre Mafra picture Alexandre Mafra · Dec 6, 2011

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.