It is very common when entering a search string in a textbox to get a small list of valid search results that match the search string entered so far. This feature is called auto complete and it has been widely used in web pages and desktop applications alike.
From the developer’s point of view, the auto complete feature has been implemented as part of the TextBox control in Windows Forms. That being said, I had a bit of a surprise when I could not find the functionality in the Windows Presentation Foundation TextBox control. Upon further online searches on the matter, I discovered that, in fact, auto complete has not been implemented for textboxes in WPF. The purpose of this post is to suggest (no pun intended) an implementation for the missing feature.
The entity we wish to create is a WPF user control that can be instantiated both through Xaml and code (C# or VB). We can either inherit from TextBox and add properties and methods required for auto complete, or we can create a control that can "wire up" the auto complete feature for any targeted TextBox control. The first approach not recommended due to the fact that a ListBox must be added to a TextBox. I chose to follow the second approach, perhaps being inspired by the AutoCompleteExtender in the ASP.NET AJAX Control Toolkit and also because it requires minimum intervention on existing WPF code (no need to change the type of many TextBox controls). Also, I did not include the targeted TextBox inside the custom control, in order to be able to apply this functionality on already existing code. The variant with the TextBox included can be found here.
A finite number of suggestions should be displayed as soon as a minimum number of characters are typed, and then the suggestion list should be updated with each new modification to the search string. The maximum number of suggestions displayed should be configurable by means of a property. The user should be able to highlight a suggestion using the up/down keys, and be able to select a suggestion by pressing Tab or Enter in order to set the text of the target TextBox to that value. The means by which the suggestions are returned (from a web service, or simple method, etc.) should be as configurable as possible.
The starting point for the TextBoxAutoCompleteProvider is a UserControl. This control will contain a Popup (named pop) and a ListBox (named lstBox) inside the Popup. the Popup is used to hide and show the suggestions list next to the targeted TextBox.
The control will use dependency properties instead of classic properties in order to facilitate data binding. It will also implement the INotifyPropertyChanged interface in order to automatically update data bindings. A routed event named SelectionChanged is defined in order to be raised every time the selected suggestion changes.
The DisplayMemberPath and SelectedValuePath are strings representing properties available in the objects returned as suggestions. If the strings are empty or null, then the ToString method will be used. The SelectedItem and SelectedValue properties connect directly to their namesakes from the ListBox. The MovesFocus property determines whether the focus changes to the next control after selecting a suggestion. The MinTypedCharacters property sets the minimum length of the search string in order to start displaying suggestions. MaxResults controls the maximum number of suggestions to be displayed and AvailableSuggestions shows the number of suggestions currently displayed in the ListBox (it might be smaller than MaxResults).
SearchMethod is a delegate of type AutoCompleteEventHandler, used to store the method that will be called in order to obtain suggestions. The delegate signature takes the search string and the maximum number of results as parameters and returns a collection of objects. In order to be as least restrictive as possible, the type of collection returned is IEnumerable. By using a delegate, the source of the data behind the suggestions can be arbitrary (database, web service, memory objects, etc.).
The main property of the control is TargetControl, of type TextBox. Whenever the value of the property changes, the event handlers are removed from the old TextBox and added to the new one. The code for those event handlers, together with the code that makes the changes is displayed below.
The private Suggest method gets called whenever the targeted TextBox receives the input focus, or the search string has changed as a result of user input. This method is responsible for invoking the method stored in the SearchMethod delegate if some conditions are met (minimum number of characters, Search method and TargetControl specified).
When a suggestion is selected (either by pressing Enter, Tab or by using the mouse), reflection is used to obtain the value of the property specified by DisplayMemberPath within the SelectedItem and to set the Text of the TextBox to this value. If DisplayMemberPath is empty or null, then the ToString method is used to fill in the TextBox.
The other properties defined by TextBoxAutoCompleteProvider offer customization options for the list of suggestions (ItemTemplate, ItemTemplateSelector, ItemsPanel).
Source code for the whole project, as well as a sample application can be found here.
The auto complete functionality is not difficult to implement, and by not extending the functionality of TextBox, applying auto complete to new and existing projects is easy. The only restriction is that one cannot specify the method to store in the SeachMethod delegate from Xaml code, only from C#(or VB.NET). Further development can be targeted at providing more customization properties for the suggestion list and at creating a converter from string to a delegate, in order to be able to specify the search method from Xaml code.
After the feeback provided, I changed the control to allow selecting an option in the ListBox through mouse clicks. The new source code is available here. Tracking the position of the target control is not supported for the Popup control. An adorner would have to be used for this. If I find the time to redesign the control to use adorners, then I will publish the update.
After Harry’s comment, I fixed a bug that would reset the SelectedIndex property on the list box. Also I exposed the SelectedIndex property. The new source code is available here.