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);
}

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

24 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?

donaldo

Posted on 8/20/2008 @ 7:31 AM
I do not see the demo at the bottom of the page. I am running SilverLight 2 Beta 2.

Ciaran Murphy

Posted on 9/25/2008 @ 3:34 AM
Can't see it either... downloaded the code and upgraded it to Beta 2 and it won't compile (WebClient isn't recognised even though correct namespace is used)
Pity it looks really nice.

Ben Griswold

Posted on 9/25/2008 @ 5:59 PM
I downloaded the code and I was able to get it to compile by adding the System.NET reference to the Framework project and updating the form.cs RegisterAttached final parameters to PropertyMetadata objects (for example, replace with "new PropertyMetadata(OnAutoCompleteChanged)". Though everything builds without issues, the web page isn't rendering the plugin. The result is the same as found at the bottom of the post. The code is great and I'm sure the functionality is too. I just can't get it work. It goes without saying that a code update would be wonderful. Thanks.

ahdjkfas

Posted on 3/9/2009 @ 4:13 PM
wonderful website.It's worth watching. Welcome to www.warestrade.com wholesale and retail!!!

ahdjkfas

Posted on 3/9/2009 @ 4:14 PM
wonderful website.It's worth watching. Welcome to www.warestrade.com wholesale and retail!!!

ahdjkfas

Posted on 3/9/2009 @ 4:14 PM
wonderful website.It's worth watching. Welcome to www.warestrade.com wholesale and retail!!!

ahdjkfas

Posted on 3/9/2009 @ 4:14 PM
wonderful website.It's worth watching. Welcome to www.warestrade.com wholesale and retail!!!

CraigD

Posted on 5/28/2009 @ 5:59 AM
I get an exception "System.InvalidOperationException: AutoComplete requires the root visual to be a Screen"

[fxui:XTextBox x:Name="RunnerAutocomplete" Grid.Column="0"]
[fxui:Interaction.Behaviors]
[fxui:AutoComplete ServiceUri="/Autocomplete.ashx" /]
[/fxui:Interaction.Behaviors]
[/fxui:XTextBox]

The ASHX works (I can breakpoint in it; and also breakpoint in a Completing event on the fxui control)... but once you've typed 3 characters the above exception is thown.

I can see in AutoComplete.cs line 402 (committed xmas 2008!)

private void ShowDropDown(IList items) {
Screen screen = Application.Current.RootVisual as Screen;
if (screen == null) {
throw new InvalidOperationException("AutoComplete requires the root visual to be a Screen.");
}

but from the above post and DataEntryPage.xaml it's not clear to me _how_ to make the 'root visual a Screen'. Apologies in advance if I've missed something simple... and thanks in advance if you're able to help.

Pras

Posted on 6/5/2009 @ 4:31 AM
I am also facing the same issue as "CraigD". Please tell me how to make the code work.

M.y

Posted on 8/11/2009 @ 9:44 PM
To CraigD:

Was your problem able to be solved?
I am confronted with a similar problem now, too. If it is possible to solve it, could you teach how you solved?

Simon Mailor

Posted on 11/5/2009 @ 1:33 AM
Hi,

This is good. I came accross an excellent example of this in a Silverlight POP3 mail client the other day - http://silvermail.com.au

It's a pretty clean mail client - good for me while checking my personal email from the office... ;-)

The coolest feature is the address book... it is very similar to Microsoft Outlook, it appears to be automatically caching email addresses, and when I go to send a new email, I get auto-complete as I type in the To, Cc and Bcc fields.

Very cool indeed.

cheap ugg

Posted on 2/28/2010 @ 5:55 PM
Your writing is very elegant, very vivid and lively, I really like you, wish you continued to write better articles, I will often try to concern, oh!
Post your comment and continue the discussion.