Search code examples
wpfuser-controlscomboboxautosuggest

Is this the best way to build AutoSuggest into a WPF ComboBox?


I have a Nationality ComboBox like the one below and want to make it so that the user can type letters to narrow in on the choices. I could solve this the way I started below by adding logic in the NationalityComboBox_KeyDown method.

Is this the best way to build AutoSuggest into a ComboBox or is there a built-in way to do the same thing?

XAML:

<ComboBox x:Class="TestComboSuggest343.NationalityComboBox"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="300" Width="300">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Value}"/>
        </DataTemplate>        
    </ComboBox.ItemTemplate>
</ComboBox>

Code-Behind:

public partial class NationalityComboBox : ComboBox
{
    public NationalityComboBox()
    {
        InitializeComponent();

        Items.Add(new KeyValuePair<string, string>(null, "Please choose..."));
        Items.Add(new KeyValuePair<string, string>(null, "American"));
        Items.Add(new KeyValuePair<string, string>(null, "Australian"));
        Items.Add(new KeyValuePair<string, string>(null, "Belgian"));
        Items.Add(new KeyValuePair<string, string>(null, "French"));
        Items.Add(new KeyValuePair<string, string>(null, "German"));
        Items.Add(new KeyValuePair<string, string>(null, "Georgian"));
        SelectedIndex = 0;

        KeyDown += new KeyEventHandler(NationalityComboBox_KeyDown);

    }

    void NationalityComboBox_KeyDown(object sender, KeyEventArgs e)
    {
        SelectedIndex = 4; // can create logic here to handle key presses, e.g. "G", "E", "O"....
    }
}

Solution

  • I wrote an attached property to do that :

    public class ComboBoxAutoFilter
    {
        public static bool GetEnabled(DependencyObject obj)
        {
            return (bool)obj.GetValue(EnabledProperty);
        }
    
        public static void SetEnabled(DependencyObject obj, bool value)
        {
            obj.SetValue(EnabledProperty, value);
        }
    
        // Using a DependencyProperty as the backing store for Enabled.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty EnabledProperty =
            DependencyProperty.RegisterAttached(
                "Enabled",
                typeof(bool),
                typeof(ComboBoxAutoFilter),
                new UIPropertyMetadata(false, Enabled_Changed)
            );
    
        private static void Enabled_Changed(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            ComboBox combo = sender as ComboBox;
            if (combo != null)
            {
                if (combo.Template != null)
                    SetTextChangedHandler(combo);
                else
                    combo.Loaded += new RoutedEventHandler(combo_Loaded);
            }
        }
    
        static void combo_Loaded(object sender, RoutedEventArgs e)
        {
            ComboBox combo = sender as ComboBox;
            combo.Loaded -= combo_Loaded;
            if (combo.Template != null)
                SetTextChangedHandler(combo);
        }
    
        private static void SetTextChangedHandler(ComboBox combo)
        {
            TextBox textBox = combo.Template.FindName("PART_EditableTextBox", combo) as TextBox;
            if (textBox != null)
            {
                bool enabled = GetEnabled(combo);
                if (enabled)
                    textBox.TextChanged += textBox_TextChanged;
                else
                    textBox.TextChanged -= textBox_TextChanged;
            }
        }
    
        private static void textBox_TextChanged(object sender, RoutedEventArgs e)
        {
            TextBox textBox = sender as TextBox;
            ComboBox combo = textBox.TemplatedParent as ComboBox;
            combo.IsDropDownOpen = true;
            string text = textBox.Text.Substring(0, textBox.SelectionStart);
            combo.Items.Filter = value => value.ToString().StartsWith(text);
        }
    
    }
    

    You can use it like that :

        <ComboBox IsEditable="True"
                  local:ComboBoxAutoFilter.Enabled="True">
            <sys:String>American</sys:String>
            <sys:String>Australian</sys:String>
            <sys:String>Belgian</sys:String>
            <sys:String>French</sys:String>
            <sys:String>German</sys:String>
            <sys:String>Georgian</sys:String>
            ...
        </ComboBox>