AutoComplete for Silverlight TextBoxes

An implementation of AutoComplete functionality that you can add to your TextBoxes in XAML... ala Google Suggest and the ASP.NET AutoComplete control extender (built on top of the behavior framework).

Yesterday I blogged about using and creating behaviors in Silverlight. This post will walk you through an autocomplete behavior that will hopefully furher crystallize the behavior concept.

To recap, a behavior is simply an object that can be declaratively attached to another component to extend the built in functionality of that component. Typically a behavior will encapsulate some event handling logic. This event handling logic could have been written by the app developer in their code-behind, but moving it into a behavior makes it much more encapsulated and suited for reuse. It has the nice side-effect of making things a bit more designer-friendly by cleaning up the code-behind and turning things into a more XAML-friendly form.

Remember Google Suggest? That kicked off a spree of auto-complete implementations for use in Ajax apps including the one we did for ASP.NET Ajax (and later in the Ajax Control Toolkit). The AutoComplete behavior is very similar. It can be used to suggest various completions based on the text that a user has entered so far. This behavior allows extending the standard Silverlight TextBox control (as well as the WatermarkTextBox control). This post will cover various scenarios accomodated by the behavior I've put together.

The screenshot above illustrates the type of experience you can add to any TextBox. You can see and play with a live demo by scrolling toward the end of this post (this requires Silverlight 2). Of course, all of the code is available as well, so you can include this into your own app.

Scenario 1: Basic completion scenario
Lets assume I've got a TextBox on a form that allows your user to enter in the name of a city, and I also have a service on the server that accepts a string prefix and returns a set of matching city names as an array of strings (serialized using the JSON format). I'd like to hook the two of them together to improve the user experience of the form, by offering a list of suggestions.

Here's the most basic use of the AutoComplete behavior in XAML as a way to get started.

<TextBox x:Name="cityTextBox">
  <f:Form.AutoComplete>
    <f:AutoComplete ServiceUri="/App_Services/Cities.ashx" />
  </f:Form.AutoComplete>
</TextBox>

Done... no code required! The AutoComplete instance assigned to the Form.AutoComplete attached property listens the TextBox events and in the background fetches city suggestions, displays a dropdown list with the cities, and allows the user to pick one. The AutoComplete does all sorts of fancy things by default like not fire off a web request per keystroke, cache results etc. As you can see the declarative XAML-based usage model makes the scenario quite simple to implement.

Here's the corresponding server-side code implementing the service in Cities.ashx:

public class CityCompletionService : CompletionService<string> {

    protected override IEnumerable<string> GetItems(string prefix) {
        string[] matchingCities = ...;
        return matchingCities;
    }
}

Scenario 2: Styling the user interface
One of the most compelling aspects of XAML and Silverlight is the ability to create compelling user interfaces by restyling and reskinning controls to match the overall look and feel of your application. The AutoComplete behavior supports this by allowing you to specify a template for the dropdown used by the behavior. If I've got some customized look and feel defined in the myCustomListBox style, I can reference it as follows:

<TextBox x:Name="cityTextBox">
  <f:Form.AutoComplete>
    <f:AutoComplete ServiceUri="/App_Services/Cities.ashx">
      <f:AutoComplete.DropDownTemplate>
        <DataTemplate>
          <ListBox Style="{StaticResource myCustomListBox}" />
        </DataTemplate>
      </f:AutoComplete.DropDownTemplate>
    </f:AutoComplete>
  </f:Form.AutoComplete>
</TextBox>

Scenario 3: Going beyond strings
So far the service backing the completion experience has been returning an array of strings. I actually want to use a service that returns something more than just a string (you'll see why in the next step). For example, in this scenario, I want to return an array of CityInfo objects that contain Name, State and ZipCode properties.

Here is the updated service implementation on the server.

public class CityInfo {
    public string Name { get; set; }
    public string State { get; set; }
    public string ZipCode { get; set; }
}

public class CityCompletionService : CompletionService<CityInfo> {

    protected override IEnumerable<CityInfo> GetItems(string prefix) {
        CityInfo[] matchingCities = ...;
        return matchingCities;
    }
}

In order to implement this scenario on the client I need to do a couple of things. First I need to provide a client-side type that the AutoComplete behavior uses to deserialize the JSON into. Next I'll want to customize the ItemTemplate of the ListBox to display the additional fields. Finally, I'll want to handle the Completed event to convert a CityInfo object into text that is used to put into the TextBox once a user selects a particular city.

public class CityInfo {
    public string Name { get; set; }
    public string State { get; set; }
    public string ZipCode { get; set; }
    public string Location {
        get {
            return " (" + State + ", " + ZipCode + ")";
        }
    }
}

<TextBox x:Name="cityTextBox">
  <f:Form.AutoComplete>
    <f:AutoComplete
      ServiceUri="/App_Services/Cities.ashx"
      ServiceResultType="CityInfo"
      Completed="OnCityCompleted">
      <f:AutoComplete.DropDownTemplate>
        <DataTemplate>
          <ListBox>
            <ListBox.ItemTemplate>
              <DataTemplate>
                <StackPanel>
                  <TextBlock Text="{Binding Name}" />
                  <TextBlock Text="{Binding Location}" />
                </StackPanel>
              </DataTemplate>
            </ListBox.ItemTemplate>
          </ListBox>
        </DataTemplate>
      </f:AutoComplete.DropDownTemplate>
    </f:AutoComplete>
  </f:Form.AutoComplete>
</TextBox>

private void OnCityCompleted(object sender, AutoCompleteCompletedEventArgs e) {
    // This event handler is optional. By default e.SelectedItem.ToString()
    // is used by the AutoComplete.
    e.SelectedItem = ((CityInfo)e.SelectedItem).Name;
}

Scenario 4: Filling dependent TextBoxes
There was a reason to return CityInfo objects instead of simply city names. Lets say my form also has a TextBox for entering ZipCode. If the user selects a city from the auto-complete dropdown, I'd also like to fill the zip code for added convenience.

I can simply update my Completed event handler above to extract the ZipCode and populate the ZipCode TextBox accordingly.

private void OnCityCompleted(object sender, AutoCompleteCompletedEventArgs e) {
    zipCodeTextBox.Text = ((CityInfo)e.SelectedItem).ZipCode;
    e.SelectedItem = ((CityInfo)e.SelectedItem).Name;
}

Scenario 5: Using local computed values
So far I've been showing scenarios where the AutoComplete behavior makes a web request to get at the list of the completion items to show in its dropdown. Some times, an app might want to show a list of values computed in client-side logic. Or you may have already loaded data from a web service call, and just want to plug in that data, rather than making a separate call.

This scenario is implementable by handling the Completing event of the AutoComplete behavior which allows you to either plug in your items, or to suppress the dropdown altogether for the particular prefix text that the user has entered. For example:

<TextBox x:Name="cityTextBox">
  <f:Form.AutoComplete>
    <f:AutoComplete Completing="OnCityCompleting" />
  </f:Form.AutoComplete>
</TextBox>

private void OnCityCompleting(object sender, AutoCompleteCompletingEventArgs e) {
    string prefix = e.Prefix;

    string[] cities = ...; // code to lookup some local data using prefix
    e.SetCompletionItems(cities);
}

Live Demo
Finally, here is a demo of the AutoComplete behavior. To experiment, type in something "San" in the city TextBox to see a list of cities pop up. Select a city (such as San Francisco) to see both the city and the zip code TextBoxes get filled.

Yet another behavior... TextFilter
Actually, I've slipped in another behavior as well into the mix, just for some more fun. This is the TextFilter behavior that can also be associated with a TextBox to restrict input to say numeric characters.

<TextBox x:Name="zipCodeTextBox">
  <f:Form.Filter>
    <f:TextFilter Filter="Numbers" />
  </f:Form.Filter>
</TextBox>

Hopefully these examples really start making the behavior concept more concrete, and clear.

You can download the code, which includes the behavior framework, all the behaviors I've written about so far, as well as a sample app that demonstrates using them. Enjoy!


[ Tags: | | ]
Posted on Tuesday, 5/6/2008 @ 5:19 PM | #Silverlight


Comments

12 comments have been posted.

Achu

Posted on 5/6/2008 @ 8:00 PM
Wonderful work

expdev

Posted on 5/7/2008 @ 12:00 PM
Nice work...

Michael Washington

Posted on 5/7/2008 @ 12:01 PM
This is a great example of a real world code that demonstrates the power of Silverlight. This is the sort of example that will convince a lot of peope they need to look at this "Silverlight stuff".

hari

Posted on 5/7/2008 @ 3:48 PM
Doesn't work in FF2. After the 3rd character is typed, the bottom border of the textbox becomes thicker, but that's about it, as far as any visual changes.

Amit

Posted on 5/7/2008 @ 4:13 PM
hari,

It is case sensitive so make sure you are typing San not san in FF2.

Cheers,
Amit

Nikhil Kothari

Posted on 5/7/2008 @ 6:28 PM
The case-sensitivity is a demo shortcoming... (in terms of what the code does in terms of using the prefix to match a set of entries). Its up to the app to figure out the completion list. The app can do things in a case-sensitive manner, or even use it as a substring for that matter.

网站推广

Posted on 5/13/2008 @ 11:25 PM
Good

VILESH

Posted on 5/17/2008 @ 6:52 AM
some things are still complicated anyways kuddos for the efforts taken

Hip-Hop

Posted on 5/28/2008 @ 11:18 AM
Cool!!!!!!!!!!!!

Dennieku

Posted on 6/9/2008 @ 12:13 AM
You did a really great job here.

I added a button to my autocomplete textbox, so it looks like a combobox. When the user clicks the button, I check the IsCompleting property of your AutoComplete class to check wether the ListBox is shown or not. Dependant on this property I call the ShowDropDown or CloseDropDown methods which I made public in stead of private.

This works when I only use this button, but when I type in the textbox something which shows the ListBox and I want to close this ListBox by clicking the button, it does not work. I noticed that the IsCompleting property is False when the ListBox is shown by typing in the textbox. Did I do something wrong or is this a bug in the Popup control perhaps?

Thx for your answer.

Ajax

Posted on 6/12/2008 @ 2:32 AM
Is there an updated version of this with Beta 2.

James

Posted on 6/18/2008 @ 12:08 AM
Im getting a error, saying it cant reference the Siverligth-FX.dll?
Post your comment and continue the discussion.
 
 
 

 

 
Refresh this form if the spam-protection code is not readable, or has expired. (Your input will be preserved)