This blog post is one of three I’m posting to introducing some of the new features in the Silverlight Toolkit announced today at the PDC 2008. For more information about and downloads of the toolkit check out
Auto complete textboxes have become a common interaction model in both web and desktop applications. All the modern browsers have smart suggestions directly in the address bar. E-mail clients on the web and on your desktop will try to suggest the recipient by looking in your address book as you type. Leveraging auto complete in your application is a great way to improve the user experience by limiting errors and cutting down the amount of typing needed to complete a form.
One of the new controls in the Silverlight Toolkit is the AutoCompleteBox control, and in this post I’m going to show how to implement auto completion it in the Dive Log application. The AutoCompleteBox control supports both data bound suggestions as well as custom code to get suggestions asynchronously, for instance by calling a web service. I will cover both scenarios and show how the auto completion code fits in to the Model-View-ViewModel architecture of the Dive Log application.
The AutoCompleteBox control looks and feels like a textbox, and has a Text-property. This makes it easy to upgrade any existing application that uses textboxes to use the AutoCompleteBox control instead. In the Dive Log application I’m going to upgrade the location and buddy textboxes to AutoCompleteBox controls. The auto complete suggestions for dive buddy will be data bound to local values, while the suggestions for dive location will be retrieved from the Dive Log web service.
Data Bound Auto Complete Suggestions
For safety reasons you never dive alone and always have a buddy with you. There is a good chance you will do multiple dives with the same diver, and having to type in the full name every time you log a new dive can be bothersome. To improve the user experience I’m going to replace the textbox with the AutoCompleteBox control. The suggested values for auto completion are going to be a list of all the buddies the user previously has dived with. If this is the first time with a new buddy the user simply types in the name as in any textbox, and for the next dive this name will be included in the auto complete text box.
The Dive Log application uses the Model-View-ViewModel pattern to keep things nice and separated. All UI components on screen are data bound against properties on the ViewModel. One of the properties is a list of all the logged dives for the currently logged on user. For the AutoCompleteBox control we need to expose a new property returning a list of all dive buddies. This is implemented as a simple LINQ query. The ViewModel implements the INotifyPropertyChanged interface, so every time the list of dives changes we need to fire the event to signal that the list of Buddies has changed.
Now that we have prepared the ViewModel with this new property we can replace the buddy textbox with the AutoCompleteBox control. The AutoCompleteBox control has a Text-property just like the textbox. The name of the buddy for the selected dive is two-way data bound against the Text-property on the AutoCompleteBox control. The AutoCompleteBox has an ItemsSource-property that we bind against the new Buddies-property on the ViewModel.
When we run the application and start typing the name of our dive buddy the AutoCompleteBox control will make suggestions based on the values in the data bound list. I haven’t applied any styling to the control, so that the moment the UI looks a little bit off. We will deal with that later.
Auto Complete Suggestions from a web services
In some cases the list of possible matches is too big to be kept in memory and loaded when the application starts. Examples of this could be the Amazons book database or auto completion in an e-mail client where you have a shared address book with thousands of contacts. In these cases you need to query the server for auto complete suggestions as the user types to only get relevant suggestions. For the Dive Log application we’re going to implement code to call a web service to get suggestions for dive locations. There is a good chance that someone else have been to a location before you, so by querying the entire Dive Log database we can provide better auto complete suggestions, and at the same time save bandwidth as we don’t have to download everything at once.
To implement asynchronous suggestions in the AutoCompleteBox control you need to subscribe to the Populating-event of the control. This event will let you run code when the control is trying to populate the list of suggestions. In the event handler you need to set the Cancel-property on the event argument to false before calling your web service to get suggestions. The event argument also contains a Paramter-property holding whatever text the user has typed already. We pass this parameter to the web service to get dive locations that match the text the user have typed in.
I had some problems deciding where to put the code to populate the AutoCompleteBox control. The code needs to have a reference to the AutoCompleteBox control to hook the Populating-event, set the ItemsSource-property and call the PopulateComplete-method when the results are ready. The most obvious place to put this code is directly in the code behind of the page. However, I always try to keep the amount of code in View to an absolute minimum, so my second attempt was to put the code responsible for handling the event and calling the web service in a separate class. The class took the AutoCompleteBox control in the constructor to get a reference to the control. Then in code-behind I would simply instantiate the class and pass in the AutoCompleteBox control to wire things up. This is a better solution than having everything in code-behind, but I’m still not happy. The class responsible for managing auto complete suggestions also needs a reference to the web service client to get suggestions from the server, and to not break testability I want to provide this dependency in the constructor.
The solution I ended up with has a separate class responsible for managing auto complete suggestions, but the reference to the AutoCompleteBox control is set using a property instead of the constructor. In the setter the class will register the event listener for the Populating-event. The class also takes the IDiveServiceClient interface in the constructor so that I can use the Ninject IoC-container to create the object.
The final problem is how to set the AutoCompleteBox property saying which control the class is providing auto complete suggestions for. I implement an attached dependency property which allows me to use XAML to link the AutoCompleteBox control against the class responsible for managing auto complete suggestions. The implementation of the dependency property I similar to the Command-pattern implementation used in the Dive Log application. I’m not going to include the full implementation here, as the source code is available for download.
The web service method returning suggestions is incredibly simple. It takes a prefix argument and runs a simple LINQ query getting all dive locations starting with that prefix.
Styling the AutoCompleteBox
Styling the AutoCompleteBox to make it fit with the look and feel of the Dive Log application is really simple. If we use Expression Blend to edit a copy of the default control template we see that the core pieces of the AutoCompleteBox is a TextBox, a Popup and a ListBox. The Popup shows suggestions in the ListBox as the user types in the TextBox. So if you know how to style a TextBox and a ListBox you got the skills needed to style the AutoCompleteBox. I would recommend styling the ListBox independently, as you cannot see the suggestion ListBox in design mode.
Since the Dive Log application already have predefined styles for TextBoxes and ListBoxes it didn’t take long to make the AutoCompleteBox look just right. I simply had to apply the excising styles and tweak the border control around the ListBox to make it look nice.