WPF autocomplete textbox/combobox

by Sottje 28. February 2010 18:38

You know these nice ajax/javascript textboxes where you start typing somes text and the website looks up the data in the database and returns a list of items starting with the text.

There is no default control for this in WPF as well. So, time to build one.

First we need to create a WPF User Control.

This control uses a canvas in stead of a grid so the control doen't resize when the results are shown. The ListBox is placed under the TextBox using a Margin. The Panel.ZIndex is used to make sure the results are shown over the other control in the window.

<UserControl x:Class="AutoCompleteComboBox.Controls.AutoCompleteComboBox"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Panel.ZIndex="10">
    <Canvas>
        <TextBox x:Name="autoTextBox" 
                 Height="25" MinWidth="150" 
                 Margin="0,0,10,0" TextWrapping="NoWrap" />
        <ListBox x:Name="suggestionListBox"
                 SelectionChanged="suggestionListBox_SelectionChanged"
                 Background="LightYellow" 
                 Visibility="Collapsed" 
                 MinWidth="150" 
                 Margin="0,25,0,-25"/>
    </Canvas>
</UserControl>

In the constructor of the control whe attach a number of events. The TextChanged event on the TextBox is keep looking up the data based on the currect value in the TextBox.
The PreviewKeyDown handles the navigation in the controls and the SelectionChanged on the ListBox updates the text in the TextBox when a item is selected. 

    /// 
    /// Interaction logic for AutoCompleteComboBox.xaml
    /// 
    public partial class AutoCompleteComboBox : UserControl
    {
        #region Constructor
        /// 
        /// Initializes a new instance of the  class.
        /// 
        public AutoCompleteComboBox()
        {
            InitializeComponent();

            // Attach events to the controls
            autoTextBox.TextChanged += 
                new TextChangedEventHandler(autoTextBox_TextChanged);
            autoTextBox.PreviewKeyDown += 
                new KeyEventHandler(autoTextBox_PreviewKeyDown);
            suggestionListBox.SelectionChanged += 
                new SelectionChangedEventHandler(suggestionListBox_SelectionChanged);
        }
        #endregion
    }

The Control had 2 public properties. The ItemsSource and SelectedValue. They are both dependency properties becaus we do want to Bind to them.

 
        #region Properties

        /// 
        /// Gets or sets the items source.
        /// 
        /// The items source.
        public IEnumerable ItemsSource
        {
            get { return (IEnumerable)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ItemsSource.  
        // This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ItemsSourceProperty =
            DependencyProperty.Register("ItemsSource"
                                , typeof(IEnumerable)
                                , typeof(AutoCompleteComboBox)
                                , new UIPropertyMetadata(null));

        /// 
        /// Gets or sets the selected value.
        /// 
        /// The selected value.
        public string SelectedValue
        {
            get { return (string)GetValue(SelectedValueProperty); }
            set { SetValue(SelectedValueProperty, value); }
        }

        // Using a DependencyProperty as the backing store for SelectedValue.  
        // This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SelectedValueProperty =
            DependencyProperty.Register("SelectedValue"
                            , typeof(string)
                            , typeof(AutoCompleteComboBox)
                            , new UIPropertyMetadata(string.Empty));

        #endregion

The magic happens in the TextChanged event. If the TextBox has a value, we use LinQ to query the ItemsSource and bind the resultset to the ListBox. Remark, the TextBox freezes while querying the database/source.

 
        /// 
        /// Handles the TextChanged event of the autoTextBox control.
        /// 
        /// The source of the event.
        /// The instance containing the event data.
        void autoTextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            // Only autocomplete when there is text
            if (autoTextBox.Text.Length > 0)
            {
                // Use Linq to Query ItemsSource for resultdata
                string condition = string.Format("{0}%", autoTextBox.Text);
                IEnumerable results = ItemsSource.Where(
                    delegate(string s) { return s.ToLower().StartsWith(autoTextBox.Text.ToLower()   ); });

                if (results.Count() > 0)
                {
                    suggestionListBox.ItemsSource = results;
                    suggestionListBox.Visibility = Visibility.Visible;
                }
                else
                {
                    suggestionListBox.Visibility = Visibility.Collapsed;
                    suggestionListBox.ItemsSource = null;
                }
            }
            else
            {
                suggestionListBox.Visibility = Visibility.Collapsed;
                suggestionListBox.ItemsSource = null;
            }
        }

To scroll through the resultset, select and item of to cancel results, we use the PreviewKeyDown event.

 
        /// 
        /// Handles the PreviewKeyDown event of the autoTextBox control.
        /// 
        /// The source of the event.
        /// The instance containing the event data.
        void autoTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Down)
            {
                if (suggestionListBox.SelectedIndex < suggestionListBox.Items.Count)
                {
                    suggestionListBox.SelectedIndex = suggestionListBox.SelectedIndex + 1;
                }
            }
            if (e.Key == Key.Up)
            {
                if (suggestionListBox.SelectedIndex > -1)
                {
                    suggestionListBox.SelectedIndex = suggestionListBox.SelectedIndex - 1;
                }
            }
            if (e.Key == Key.Enter || e.Key == Key.Tab)
            {
                // Commit the selection
                suggestionListBox.Visibility = Visibility.Collapsed;
                e.Handled = (e.Key == Key.Enter);
            }
                
            if (e.Key == Key.Escape)
            {
                // Cancel the selection
                suggestionListBox.ItemsSource = null;
                suggestionListBox.Visibility = Visibility.Collapsed;
            }
        }

When the selected item has changed, the SelectedValue needs to be set. Make sure to disable the TextChanged event to avoid an update on the list.

 
        /// 
        /// Handles the SelectionChanged event of the suggestionListBox control.
        /// 
        /// The source of the event.
        /// The instance containing the event data.
        private void suggestionListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (suggestionListBox.ItemsSource != null)
            {
                autoTextBox.TextChanged 
                    -= new TextChangedEventHandler(autoTextBox_TextChanged);
                if (suggestionListBox.SelectedIndex != -1)
                {
                    autoTextBox.Text = suggestionListBox.SelectedItem.ToString();
                }
                autoTextBox.TextChanged 
                    += new TextChangedEventHandler(autoTextBox_TextChanged);
            }
        }

The solution can be downloaded here: AutoCompleteComboBox.zip (17,55 kb)

kick it on DotNetKicks.com

Tags: , , ,

Professional | c# | WPF

About me

Jeroen van Gent is a Professional Web, Windows and Surface developer at Qurius.

Currently coordinating 3rd line international support for an accounting company active in over a 100 countries for a WPF/Groove .NET application used by more than 20.000 users on a daily basis.

Husband and Parent with passion for Reading, Star Wars and Lego!

Follow me on twitter: twitter.com/sottje