Introduction
I was searching for a TextBox
with autocomplete capabilities considering substring matches. The solutions I found were little clumsy, so here is my rather simple, but stable solution.
Background
This is my first article, so don't expect this to be perfect. The code comments are overkill, I think, but this makes the source self explaining.
Well, there are a few differences between other approaches. At first, we use a Panel
instead of a Form
to display a list of suggestions. Secondly, we use IndexOf
instead of Linq
to find the matches. Thirdly, we use pure safe .NET code only, no Win32 Calls and no sloppy threading, e.g. sleep()
, or other stuff.
Behind the Scenes
In the main Form's Load
event, we read a text file called "en-EN.dic", which is a dictionary with more than 50,000 entries. It is to be stored it in the component's AutoCompleteList
property, which is a List<string>
.
While the AutoCompleteList
is used as "database" and remains unchanged, the CurrentAutoCompleteList
property contains a subset of appropriate candidates to be shown as suggestions in a ListBox
beneath.
The main work is done in a method called ShowSuggests()
which calls a time critical method named UpdateCurrentAutoCompleteList()
that calls the second time critical method UpdateListBoxItems()
. I mention this because the internal list of elements to be shown and the list finally added to the image box are both time consuming operations that may cause laggy responses. So why are we using two lists for our data? Well, this is not the last word spoken, but I found DataSource
/ DataContext
to be faster than adding single items to the listbox
in the substring query (see if ((Str.IndexOf(this.Text) > -1))
).
Using the Code
The component is used like a normal TextBox
, except that it has some "special" properties that can be set (CaseSensitive
, MinTypedCharacters
and the AutoCompleteList
). A sample of the usage can be found in the Form's Load
event. In brief: Drop it into a Form
and assign a List<string>
of phrases to the AutoCompleteList
property.
Some Things to be Mentioned
I have done some performance measuring and alternative implementations, so you may switch between an ArrayList
or a List<string>
basis. I have chosen the second one because it is a generic type. However I found the ArrayList
to perform a little better on large data (dictionary at about 10MB size). Maybe you would like to take this further, see the comments.
You will find two digressions, named excursions (bad English), like this in the source:
#region Digression: Performance measuring of Linq queries
#endregion Digression: Performance measuring of Linq queries
The code within these regions is uncommented and meant for comparison of alternative implementations in the case shown above, to measure the performance of an alternative Linq query.
The other digression is about switching to a method using AddRange
to fill the list of suggestions. In the example code, the default method used is a manual update of the BindingContext
. If you experience problems with that, just feel free to choose the other approach.
Here is how the manual updating of the BindingContext
of the ListBox
works:
listBox.DataSource = CurrentAutoCompleteList;
((CurrencyManager)listBox.BindingContext[CurrentAutoCompleteList]).Refresh();
The rest is just about correct timing and knowing when to show components and how to handle key and mouse events for two components in one. So we take care of KeyDown
for PageUp
and PageDown
keys, MouseClick
and MouseDoubleClick
events. And we ensure the component to fit into the ParentForm
, avoid flickering and overlapping and so on.
Happy coding!