This article is also available in print (Word, PDF) and e-reader formats (MOBI, EPUB).
Download all formats, including source code for $0.99.
This article is compatible with the latest version of Silverlight.
The auto-complete box is a mix of a normal text box that allows the user to type in arbitrary text, and the drop-down list feature of a combo box that provides a fixed set of entries the user can select from. You're most likely using a control like this every day; for example, search engines like Bing and other web sites use these controls to provide a list of suggestions you can pick from without having to finish typing the full term, or they even provide a list of terms you entered previously to make repeated entries more comfortable for you.
The fact that the list of entries is filtered on the fly as the user types into the text box makes picking an entry from a large list possible very quickly. Even if users only remember parts of an entry name they can reduce the number of potential candidates dramatically by typing just a few characters. This is significantly different from other controls that only scroll to entries that start with what you type in, for example. In this article we'll see how the auto-complete box works and what options it offers; and although most people associate the control with string values only, we'll see that it's capable of handling far more than that and that it can work with any kind of data with little effort.
As always, you can download the complete source code at the end of the article. It contains all of the following samples as individual projects.
Basic usage
For the simplest scenario, you only need to provide the auto-complete box with an items source and process changes of the SelectedItem property either through data binding or by handling the SelectionChanged event. The source items do not necessarily need to be strings like in the following example. The control will automatically call the ToString() method on the items to create the text entries for the drop-down list, so if your data items override this method and a string representation is sufficient, you don't need to do anything else to get a working result. Alternatively, you can also hint the auto-complete box at what property shall be used for the string representation, as we'll see in a moment.
<StackPanel x:Name="LayoutRoot" Background="White" Width="100">
<TextBlock Text="{Binding ElementName=MyAutoCompleteBox, Path=SelectedItem,
TargetNullValue='No item selected',
StringFormat='Selected Item: {0}'}"/>
<sdk:AutoCompleteBox x:Name="MyAutoCompleteBox"
ItemsSource="{Binding Items}" />
</StackPanel>
Items = new List<string>();
Items.Add("One");
Items.Add("Two");
Items.Add("Three");
Items.Add("Four");
Items.Add("Five");
Items.Add("Six");
Items.Add("Seven");
Items.Add("Eight");
Items.Add("Nine");
Items.Add("Ten");
This sample code will result in the following:
Surprisingly many aspects of the behavior of this control can be changed by built-in properties, with one of the most important ones being the way the auto-complete box filters the entries.
Changing the filter mode
By default, the auto-complete box filters entries by a "starts with" pattern. This means that if the string representation of an item starts with what is typed into the text box, it will be listed in the drop-down area. You can change this behavior with the FilterMode property of the control, which can be one of the predefined values of the AutoCompleteFilterMode enumeration. By changing this property, you can change the match algorithm to a "contains" or "equals" pattern and also use case-sensitive filtering instead of the default case-insensitive behavior.
Automatic text completion
One interesting property is the IsTextCompletionEnabled parameter. If you set this to true, the first possible match in the drop-down list is automatically selected and displayed in the textbox. To preserve fluent typing capabilities, the part of the match that wasn't typed in by the user is selected, so they can overwrite it if they just keep typing.
One drawback of this is that this actually changes the SelectedItem property whenever a different match from the drop-down list is selected and put into the text box. This is potentially undesired, because it might trigger additional (expensive or otherwise time-consuming) logic in your (view) model. Maybe that is the reason why the automatic completion is turned off by default.
Other behavioral properties
If you have a very large list of source items looking up potential matches may be expensive, or might result in a similar large list which doesn't really help the user much. In these cases, you can restrict the number of minimum characters the user has to type in until the drop-down list is populated with possible matches. If you set the corresponding property MinimumPrefixLength to a value of 3 for example, no list will be shown if the user has typed in less than three characters. The default value is 0.
A similar property is MinimumPopulateDelay (again the default value is 0) which allows to set the number of milliseconds that must expire after the user has typed in some text to kick of the population of the drop-down list. This is particularly useful in scenarios where you fetch the data for the drop-down list from a web service or similar (see below) and you need to minimize the number of network round-trips.
Styling of the AutoCompleteBox
Of course you can change the control template of the auto-complete box to change major parts of it. But in addition, the control exposes three properties that you can use directly to change the visual appearance of some of its inner parts more easily. Those three properties are: TextBoxStyle, ItemContainerStyle and ItemTemplate. They allow you to provide individual styles for the text box and the list box items. More advanced properties of the contained list box (like the items panel for example) are not exposed directly, but these three properties give you a lot of flexibility already. I won't go into the details of styling the control, as it's the same as styling stand-alone text boxes and list boxes, and also the linked MSDN documentation for the text box and item container style properties contain a full example for that. We'll use the item template to customize the control in the following parts though.
Advanced note: Although the auto-complete box very much behaves like an items control and offers some of the same properties like items source and item template, it actually does not inherit from the items control base class like the combo box does, for example. The reason for this is that the auto-complete box does not have a strong relation to a particular control for the drop-down part. The only requirement is that this selector implements the ISelectionAdapter interface. This means that you can change the default list box used for this to any control, even custom controls you've developed yourself. An auto-complete box using a tree view or similar is definitely a possible scenario. This gives you even more flexibility when you need to customize the control. However, this is an advanced topic that requires deriving from the auto-complete box and creating the actual selection adapter which I won't cover in this article.
Using custom data and an item template
Like I already pointed out you can use the auto-complete box with non-string data. This includes data that you want to present in a visually appealing way with more than just simple text entries. To this end, you can use the item template to specify how the items in the drop-down list will appear. Take the following simple model, for example:
public class MyData
{
public Uri ImageUri
{
get;
set;
}
public string Name
{
get;
set;
}
}
To show the images referenced in these items, you can create an item template like the following:
<sdk:AutoCompleteBox ItemsSource="{Binding Data}"
ValueMemberPath="Name"
FilterMode="ContainsOrdinal">
<sdk:AutoCompleteBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ImageUri}" />
<TextBlock Text="{Binding Name}"
VerticalAlignment="Center"
Margin="10 0 0 0" />
</StackPanel>
</DataTemplate>
</sdk:AutoCompleteBox.ItemTemplate>
</sdk:AutoCompleteBox>
An important detail to note here is the use of the ValueMemberPath property. When you work with complex types you need to tell the control what property should be used for a) the display in the text box, and b) in the filtering process for the items in the drop-down list. In this particular sample, we show both the image and name in the drop-down list, but in the text box and for the filtering, only the name property of our data class is used. Alternatively you can use the ValueMemberBinding property to use a binding instead of a property path, for example when you want to use a value converter. The result of the above example is something like this:
Using a custom filter method
In some cases, the built-in capabilities of the auto-complete box may not be sufficient for your scenario. Imagine a situation where you want to filter your list of source items by multiple property values. For example, users may want to search a database of electronic components by both the part name or the part number. Then you would have to compare the input of the text box to both properties to determine which possible candidates should be visible in the drop-down list. Luckily, the control offers two extension points for that too: the TextFilter and ItemFilter properties. Both offer the possibility to provide custom methods that are called to filter your items source. The first one passes in the string representation of the item to test, the second one in contrast passes in the original items used to bind the control in the first place. Let's see an example of how this works.
I'm using the same data class as in the sample before. This time, the XAML looks slightly different though:
<sdk:AutoCompleteBox x:Name="MyAutoCompleteBox"
ItemsSource="{Binding Data}"
ValueMemberPath="Name"
ItemFilter="{Binding FilterItem}">
<sdk:AutoCompleteBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ImageUri}" />
<TextBlock Text="{Binding Name}"
VerticalAlignment="Center"
Margin="10 0 0 0" />
</StackPanel>
</DataTemplate>
</sdk:AutoCompleteBox.ItemTemplate>
</sdk:AutoCompleteBox>
Note that I bind the ItemFilter property to a property of my view model here. This property is just a simple wrapper around the actual filter method:
public AutoCompleteFilterPredicate<object> FilterItem
{
get
{
return DoFilterItem;
}
}
private bool DoFilterItem(string search, object data)
{
// get the original item
var myData = data as MyData;
if (myData == null)
{
return false;
}
// list the item if the name length is equal
// to the search term length
return search.Length == myData.Name.Length;
}
As you can see, the method signature for the filtering takes the search term and the current item to inspect as arguments. You need to return a bool value that determines whether the item will be included in the drop-down list (true) or not (false). In this case, I only return those items whose names have the same length like the search term. Not very meaningful, but it shows how you can do filtering the auto-complete box does not support out of the box. The formal documentation for the filter methods is described here.
Asynchronous filtering
One thing people frequently ask is how to do asynchronous filtering. A particular requirement often is to fetch possible candidates for the drop-down list from a web service. Doing this is surprisingly easy with the auto-complete box. To aid this, it offers two things. First of all there is the Populating event. It is raised when the population of the drop-down list is about to start. The important part is that you have the possibility to cancel the populating by setting the appropriate property of the event arguments in your event handler. This is the optimal place and way to start your asynchronous service call. The second piece of the puzzle is the PopulateComplete method. Call this method to signal to the auto-complete box that you are finished setting up the items source and that it can show the drop-down list now. In the end, this comes down to very little code, like this:
private void MyAutoCompleteBox_Populating(object sender, PopulatingEventArgs e)
{
// create a web client and initiate the service call
FilterServiceClient client = new FilterServiceClient();
client.FilterCompleted += new EventHandler<FilterCompletedEventArgs>(ServiceClient_FilterCompleted);
client.FilterAsync(e.Parameter);
// cancel populating
e.Cancel = true;
}
private void ServiceClient_FilterCompleted(object sender, FilterCompletedEventArgs e)
{
// set up the new items source
// and tell the auto-complete box to finish populating
MyAutoCompleteBox.ItemsSource = e.Result;
MyAutoCompleteBox.PopulateComplete();
}
As you can see, all that happens is the web service call is started in the populating event, and when it returns, the auto-complete box is told to finish the previously canceled populating. The corresponding XAML for this is:
<sdk:AutoCompleteBox x:Name="MyAutoCompleteBox"
MinimumPopulateDelay="500"
Populating="MyAutoCompleteBox_Populating" />
Please note the use of the MinimumPopulateDelay property I've discussed above. To avoid that my service is being hit on every keystroke I've added a delay of 500 milliseconds here. This means that the populating event will not be raised more often than once per half second.
Bonus: MVVM-friendly asynchronous filtering
The way the above mentioned procedure works is not very MVVM friendly. You have the possibility to bind commands of your view model to events with built-in helpers like triggers and actions (or probably your MVVM framework has its own helpers for this); however, for the auto-complete box this is not enough. You need to change a property value of the very specific populating event arguments as well as call a method on the control when you have finished the filtering process. To avoid tight coupling between the view model and auto-complete box (as it would be introduced by passing references of event arguments or the control itself to your view model) I have written a behavior that acts as the glue between both. This only is one of many possibilities to handle this situation, but for my requirements so far it was sufficient.
First of all I created a parameter data container class to pass around the required information. In particular, the FilterCriteria property will contain the string the user entered into the text box. The FilterComplete property is an abstraction of the PopulateComplete method the auto-complete box offers.
public class FilterAsyncParameters
{
public Action FilterComplete
{
get;
private set;
}
public string FilterCriteria
{
get;
private set;
}
public FilterAsyncParameters(Action filterComplete, string filterCriteria)
{
FilterComplete = filterComplete;
FilterCriteria = filterCriteria;
}
}
Next I created a behavior that is typed to the AutoCompleteBox. This behavior has a dependency property of type ICommand that can be used to bind a command of the view model which should be executed when asynchronous filtering has to be performed.
public class FilterAsyncBehavior : Behavior<AutoCompleteBox>
{
public ICommand FilterAsyncCommand
{
get
{
return (ICommand)GetValue(FilterAsyncCommandProperty);
}
set
{
SetValue(FilterAsyncCommandProperty, value);
}
}
public static readonly DependencyProperty FilterAsyncCommandProperty = DependencyProperty.Register("FilterAsyncCommand",
typeof(ICommand),
typeof(FilterAsyncBehavior),
new PropertyMetadata(null));
...
The behavior overrides the OnAttached and OnDetaching methods to add and remove an event handler for the populating event of the associated auto-complete box. In the event handler, the populating is canceled and the command on the view model is executed. The behavior creates a new instance of the custom parameters class to pass both the search string as well as the PopulateComplete method (disguised as FilterComplete action) to the view model.
protected override void OnAttached()
{
base.OnAttached();
// handle the populating event of the associated auto complete box
AssociatedObject.Populating += AssociatedObject_Populating;
}
protected override void OnDetaching()
{
// detach the event handler
AssociatedObject.Populating -= AssociatedObject_Populating;
base.OnDetaching();
}
private void AssociatedObject_Populating(object sender, PopulatingEventArgs e)
{
// get the command
ICommand filterCommand = FilterAsyncCommand;
if (filterCommand != null)
{
// create the parameters for the command
var parameters = new FilterAsyncParameters(AssociatedObject.PopulateComplete, e.Parameter);
// execute command
filterCommand.Execute(parameters);
// cancel the population of the auto complete box
e.Cancel = true;
}
}
That's basically all. You can then use the behavior in XAML like this:
<sdk:AutoCompleteBox x:Name="MyAutoCompleteBox"
MinimumPopulateDelay="500"
ItemsSource="{Binding Data}">
<interactivity:Interaction.Behaviors>
<utilities:FilterAsyncBehavior FilterAsyncCommand="{Binding FilterAsyncCommand}" />
</interactivity:Interaction.Behaviors>
</sdk:AutoCompleteBox>
Of course your view model has to provide a property and some kind of implementation for the FilterAsyncCommand. In my case, I've used the RelayCommand class of MVVM light to relay execution to a method in my view model. The code is pretty similar to what we had in the code-behind file of the main page before, but of course without interacting with the auto-complete box directly.
public MainViewModel()
{
FilterAsyncCommand = new RelayCommand<FilterAsyncParameters>(ExecuteFilterAsync);
}
private void ExecuteFilterAsync(FilterAsyncParameters args)
{
// start the async web service call
FilterServiceClient client = new FilterServiceClient();
client.FilterCompleted += new EventHandler<FilterCompletedEventArgs>(ServiceClient_FilterCompleted);
client.FilterAsync(args.FilterCriteria, args);
}
private void ServiceClient_FilterCompleted(object sender, FilterCompletedEventArgs e)
{
// set the new data
Data = e.Result;
// trigger the filter complete action
var filterArgs = e.UserState as FilterAsyncParameters;
filterArgs.FilterComplete();
}
So, with little effort you can remove all code-behind even for asynchronous filtering and provide a more MVVM-friendly version of the sample.
Summary
Hopefully this article has helped you realize that with the AutoCompleteBox really comes great flexibility. The built-in features already cover a lot of the most common situations, and it offers various extension points for customization and to plug in your own filter logic, even for advanced scenarios when you want to fetch the potential matches from a web service. You can download the complete source code that contains all the sample projects here:
Download source code
About the author
Peter Kuhn aka "Mister Goodcat" has been working in the IT industry for more than ten years. After being a project lead and technical director for several years, developing database and device controlling applications in the field of medical software and bioinformatics as well as for knowledge management solutions, he founded his own business in 2010. Starting in 2001 he jumped onto the .NET wagon very early, and has also used Silverlight for business application development since version 2. Today he uses Silverlight, .NET and ASP.NET as his preferred development platforms and offers training and consulting around these technologies and general software design and development process questions. In his free time he also works on hobby and commercial game projects. You can find his tech blog here: http://www.pitorque.de/MisterGoodcat/