Creating a simple auto-completion control with WPF

Published 03-30-2006 2:13 PM

The (WPF) project I am currently working on at Macaw requires a textbox with auto-completion support. Perhaps I am a bit blind but I simply don't seem to be able to find this feature on the TextBox control that is provided with the WPF framework, this reminds me of a bit of criticism that has been popping up here and there lately.

The thing is that somehow I have the feeling that such a feature might actually be inside the WPF framework but it isn’t really clear where one could find it, especially when you are used to wearing the WinForms hat and are expecting to see a specific feature at a specific location. 

Although the default auto completion behaviour wouldn’t have suited my needs anyway, if anybody knows how to pull off a standard auto completion TextBox in WPF,  please do let me know.  

The reason why the default auto-completion behaviour isn’t what I’m looking for is because I am building a textbox in which users can add tags separated by a comma and I need auto completion per tag. If you are familiar with the textbox that delicious uses for its tagging you will probably be able to envision what I am trying to create. For those that haven’t seen this feature, here is a screenshot of the auto completion textbox in action.

Screenshot

The first step we take is to create a simple project with a user control and a window to test our user control. Here is the xaml code for custom control. You can find the link to the download at the bottom of this article.

<UserControl x:Class="WPFAutoComplete.AutoCompletionTextBox"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
      <Canvas>
            <
TextBox
                 
x:Name="TagsTextBox"
                  Canvas.Left="0"
                  Canvas.Top="0"
                  Width="100"
                  LostFocus="TagsTextBox_LostFocus"
                  KeyUp="TagsTextBox_KeyUp" />

            <ListBox
                 
x:Name="TagsAutoCompletionListBox"
                  Visibility="Hidden"
                  FontSize="14"        
                  SelectionChanged="TagsAutoCompletionListBox_SelectionChanged" />

      </
Canvas>
</
UserControl>

As you can see we are using a TextBox control and a ListBox control which is hidden by default. You can probably imagine what we need both of these for :-) The second thing to note is that we have a few events hooked up, we need to have these in our code-behind file as well.

First of all we need three helper methods to help us to decide:

  • What is the tag we want to get the auto-completion for. We do this by splitting the Text property of the TagsTextBox control with a comma;
  • When the user selects a tag from the TagsAutoCompletionListBox list we want this to reflect in the TagsTextBox’s Text property;
  • A helper method to move to the end of the text that has been entered in the TagsTextBox and subsequently set focus to the TagsTextBox so that the user might continue entering more tags.
  • The final helper-thingie we want is a feature that can restore the user’s entered text when the user isn’t satisfied with the suggestions provided by the auto completion features by hitting the “escape” button.

These features are implemented in the methods that you see in the first half of the code-behind file.

Ok now that we have that out of the way let’s describe how we want our textbox to behave:

  • When the use is typing a tag longer then 1 character we want the auto completion features to kick in;
  • When the user presses the down arrow we want to select the next suggestion from the auto completion list and replace the tag the user was typing with the one from the list;
  • When the user presses the up arrow we want to do the opposite of the above mentioned feature;
  • When the user presses the “enter” key we want to select the active item and close the list.
  • When the user presses the “escape” key we want to restore anything that we might have replaced to the previous version (sort of a undo feasture).

The real magic happens in the following piece of code:

// Get auto-completion list for text user is typing

AutoCompletionProvider provider = new AutoCompletionProvider();
List<string> suggestedTags = provider.GetTagsForAutoCompletion(currentTag);

if (suggestedTags.Count > 0)
{
      TagsAutoCompletionListBox.ItemsSource = suggestedTags;
      TagsAutoCompletionListBox.Visibility = Visibility.Visible;

      // Get x-coordinate of TagTextBox.Text minus suggestedTags
      int count = TagsTextBox.Text.Length - GetCurrentTag().Length;

      double x = TagsTextBox.GetRectFromCharacterIndex(count).X;

      Canvas.SetTop(TagsAutoCompletionListBox, TagsTextBox.ActualHeight);
      Canvas.SetLeft(TagsAutoCompletionListBox, x);

      // Save state before opening list so we can restore this when use hits escape.
      previousText = TagsTextBox.Text;
}

else
{
      // No items to suggest, hide suggestion list
     
TagsAutoCompletionListBox.Visibility = Visibility.Hidden;
}

I have created a bogus provider for returning a couple of tags to play around a bit, this should be your call to a webservice, a database or anything else you want to use as a source.

There is a bit of interesting code that gets the location at the x-coordinate where we want the auto completion ListBox to appear:

// Get x-coordinate of TagTextBox.Text minus suggestedTags
int count = TagsTextBox.Text.Length - GetCurrentTag().Length;

double x = TagsTextBox.GetRectFromCharacterIndex(count).X;

The rest is kind of straight-forward. You can download the code and check it our yourself. It isn’t really hard to do and might need some polish here and there (look for any ToDo’s) but it can serve as a nice starting point for rolling your own auto completion features in WPF.

Hope anybody finds it useful in some way. The source-code can be downloaded here and is meant to be compiled on the March CTP, it should work just fine on any subsequent version though.


Leave a Comment

(required) 
(required) 
(optional)
(required)