Auto Complete for TextBox in WPF (MVVM)

Sarah 186 Reputation points
2022-05-08T19:36:40.633+00:00

Hi, Does anyone know an easy way to impelement Auto Complete for TextBox in WPF?

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,667 questions
0 comments No comments
{count} votes

2 answers

Sort by: Most helpful
  1. Hui Liu-MSFT 38,026 Reputation points Microsoft Vendor
    2022-05-09T09:10:41.607+00:00

    The most common autocomplete in MVVM is combobox, you could refer to the solution here.
    You can also refer to the code below to create it as a user control and then use in mvvm.
    MainWindow.xaml:

    <TextBox VerticalAlignment="Center" HorizontalAlignment="Center" x:Name="SuggestionBox" Width="200"   />  
    

    MainWindow.xaml.cs:

    using System.Linq;  
    using System.Windows;  
    using System.Windows.Controls;  
      
    namespace AutoCompleteTextBox  
    {  
      public partial class MainWindow : Window  
      {  
        private static readonly string[] SuggestionValues = {  
                "English",  
                "Chinese",  
                "Franch",  
                "Russian",  
                "Japanese"  
      
            };  
      
        public MainWindow()  
        {  
          InitializeComponent();  
          SuggestionBox.TextChanged += SuggestionBoxOnTextChanged;  
        }  
      
        private string _currentInput = "";  
        private string _currentSuggestion = "";  
        private string _currentText = "";  
      
        private int _selectionStart;  
        private int _selectionLength;  
        private void SuggestionBoxOnTextChanged(object sender, TextChangedEventArgs e)  
        {  
          var input = SuggestionBox.Text;  
          if (input.Length > _currentInput.Length && input != _currentSuggestion)  
          {  
            _currentSuggestion = SuggestionValues.FirstOrDefault(x => x.StartsWith(input));  
            if (_currentSuggestion != null)  
            {  
              _currentText = _currentSuggestion;  
              _selectionStart = input.Length;  
              _selectionLength = _currentSuggestion.Length - input.Length;  
      
              SuggestionBox.Text = _currentText;  
              SuggestionBox.Select(_selectionStart, _selectionLength);  
            }  
          }  
          _currentInput = input;  
        }  
      
      }  
    }  
    

    The result:
    200242-6.gif

    Update:
    MainWindow.xaml:

     <Grid>  
            <ComboBox x:Name="Cmb"  IsTextSearchEnabled="False" IsEditable="True"   
              ItemsSource="{Binding DataSource}"   Width="120"   Height="50" IsDropDownOpen="True"  
              StaysOpenOnEdit="True"    KeyUp="Cmb_KeyUp"  />  
      
        </Grid>  
    

    MainWindow.xaml.cs:

    using System;  
    using System.Collections.ObjectModel;  
    using System.ComponentModel;  
    using System.Runtime.CompilerServices;  
    using System.Windows;  
    using System.Windows.Data;  
    using System.Windows.Input;  
    namespace ComboboxAutoComplete  
    {  
      public partial class MainWindow : Window  
      {  
        public MainWindow()  
        {  
          InitializeComponent();  
      
          FilterViewModel vm = new FilterViewModel();  
          this.DataContext = vm;  
        }  
        private void Cmb_KeyUp(object sender, KeyEventArgs e)  
        {  
          CollectionView itemsViewOriginal = (CollectionView)CollectionViewSource.GetDefaultView(Cmb.ItemsSource);  
      
          itemsViewOriginal.Filter = ((o) =>  
          {  
            if (String.IsNullOrEmpty(Cmb.Text)) return true;  
            else  
            {  
              if (((string)o).Contains(Cmb.Text)) return true;  
              else return false;  
            }  
          });  
      
          itemsViewOriginal.Refresh();  
        }  
      }  
      public class FilterViewModel : INotifyPropertyChanged  
      {  
        private ObservableCollection<string> dataSource;  
        public ObservableCollection<string> DataSource  
        {  
          get  
          {  
            return dataSource;  
          }  
          set { dataSource = value; OnPropertyChanged("ListOfCountry"); }  
        }  
        public FilterViewModel()  
        {  
          dataSource = new ObservableCollection<string>();  
          dataSource.Add("Beijing");  
          dataSource.Add("New York");  
          dataSource.Add("Paris");  
          dataSource.Add("Shanghai");  
          dataSource.Add("London");  
          dataSource.Add("Nanjing");  
        }  
        public event PropertyChangedEventHandler PropertyChanged;  
        internal void OnPropertyChanged([CallerMemberName] string propName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));  
      }  
    }  
    

    Update 2 :
    MainWindow.xaml:
    <Grid x:Name="grid">

            <ComboBox x:Name="Cmb"  IsTextSearchEnabled="False" IsEditable="True"   
               ItemsSource="{Binding DataSource}"   Width="120"   Height="50" IsDropDownOpen="True"  
               StaysOpenOnEdit="True"    local:MyComboBox.IsFilterOnAutocompleteEnabled="True" />  
        </Grid>  
    

    CodeBehind:

    using System;  
    using System.Collections.Generic;  
    using System.Collections.ObjectModel;  
    using System.ComponentModel;  
    using System.Runtime.CompilerServices;  
    using System.Windows;  
    using System.Windows.Controls;  
    using System.Windows.Controls.Primitives;  
    using System.Windows.Input;  
    using System.Windows.Media;  
    using System.Windows.Threading;  
      
    namespace ExecuteFunctionOnSpecialTime  
    {  
      public partial class MainWindow : Window  
      {  
        public MainWindow()  
        {  
          InitializeComponent();  
          FilterViewModel vm = new FilterViewModel();  
          this.DataContext = vm;  
        }  
      }  
      public class FilterViewModel : INotifyPropertyChanged  
      {  
        private ObservableCollection<string> dataSource;  
        public ObservableCollection<string> DataSource  
        {  
          get  
          {  
            return dataSource;  
          }  
          set { dataSource = value; OnPropertyChanged("ListOfCountry"); }  
        }  
        public FilterViewModel()  
        {  
          dataSource = new ObservableCollection<string>();  
          dataSource.Add("Beijing");  
          dataSource.Add("New York");  
          dataSource.Add("Paris");  
          dataSource.Add("Shanghai");  
          dataSource.Add("London");  
          dataSource.Add("Nanjing");  
        }  
        public event PropertyChangedEventHandler PropertyChanged;  
        internal void OnPropertyChanged([CallerMemberName] string propName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));  
      }  
      public static class Extensions  
      {  
        public static bool TryFindVisualChildElement<TChild>(this DependencyObject parent, out TChild resultElement)  
          where TChild : DependencyObject  
        {  
          resultElement = null;  
      
          if (parent is Popup popup)  
          {  
            parent = popup.Child;  
            if (parent == null)  
            {  
              return false;  
            }  
          }  
      
          for (var childIndex = 0; childIndex < VisualTreeHelper.GetChildrenCount(parent); childIndex++)  
          {  
            DependencyObject childElement = VisualTreeHelper.GetChild(parent, childIndex);  
            if (childElement is TChild child)  
            {  
              resultElement = child;  
              return true;  
            }  
      
            if (childElement.TryFindVisualChildElement(out resultElement))  
            {  
              return true;  
            }  
          }  
          return false;  
        }  
      }  
      class MyComboBox : DependencyObject  
      {  
        public static readonly DependencyProperty IsFilterOnAutocompleteEnabledProperty =  
          DependencyProperty.RegisterAttached(  
            "IsFilterOnAutocompleteEnabled",  
            typeof(bool),  
            typeof(MyComboBox),  
            new PropertyMetadata(default(bool), MyComboBox.OnIsFilterOnAutocompleteEnabledChanged));  
      
        public static void SetIsFilterOnAutocompleteEnabled(DependencyObject attachingElement, bool value) =>  
          attachingElement.SetValue(MyComboBox.IsFilterOnAutocompleteEnabledProperty, value);  
      
        public static bool GetIsFilterOnAutocompleteEnabled(DependencyObject attachingElement) =>  
          (bool)attachingElement.GetValue(MyComboBox.IsFilterOnAutocompleteEnabledProperty);  
        // Use hash tables for faster lookup  
        private static Dictionary<TextBox, ComboBox> TextBoxComboBoxMap { get; }  
        private static Dictionary<TextBox, int> TextBoxSelectionStartMap { get; }  
        private static Dictionary<ComboBox, TextBox> ComboBoxTextBoxMap { get; }  
        private static bool IsNavigationKeyPressed { get; set; }  
      
        static MyComboBox()  
        {  
          MyComboBox.TextBoxComboBoxMap = new Dictionary<TextBox, ComboBox>();  
          MyComboBox.TextBoxSelectionStartMap = new Dictionary<TextBox, int>();  
          MyComboBox.ComboBoxTextBoxMap = new Dictionary<ComboBox, TextBox>();  
        }  
        private static void OnIsFilterOnAutocompleteEnabledChanged(  
          DependencyObject attachingElement,  
          DependencyPropertyChangedEventArgs e)  
        {  
          if (!(attachingElement is ComboBox comboBox && comboBox.IsEditable))  
          {  
            return;  
          }  
          if (!(bool)e.NewValue)  
          {  
            MyComboBox.DisableAutocompleteFilter(comboBox);  
            return;  
          }  
      
          if (!comboBox.IsLoaded)  
          {  
            comboBox.Loaded += MyComboBox.EnableAutocompleteFilterOnComboBoxLoaded;  
            return;  
          }  
          MyComboBox.EnableAutocompleteFilter(comboBox);  
        }  
      
        private static async void FilterOnTextInput(object sender, TextChangedEventArgs e)  
        {  
          await Application.Current.Dispatcher.InvokeAsync(  
            () =>  
            {  
              if (MyComboBox.IsNavigationKeyPressed)  
              {  
                return;  
              }  
      
              var textBox = sender as TextBox;  
              int textBoxSelectionStart = textBox.SelectionStart;  
              MyComboBox.TextBoxSelectionStartMap[textBox] = textBoxSelectionStart;  
      
              string changedTextOnAutocomplete = textBox.Text.Substring(0, textBoxSelectionStart);  
              if (MyComboBox.TextBoxComboBoxMap.TryGetValue(  
                textBox, out ComboBox comboBox))  
              {  
                comboBox.Items.Filter = item => item.ToString().StartsWith(  
                  changedTextOnAutocomplete,  
                  StringComparison.OrdinalIgnoreCase);  
              }  
            },  
            DispatcherPriority.Background);  
        }  
      
        private static async void HandleKeyDownWhileFiltering(object sender, KeyEventArgs e)  
        {  
          var comboBox = sender as ComboBox;  
          if (!MyComboBox.ComboBoxTextBoxMap.TryGetValue(comboBox, out TextBox textBox))  
          {  
            return;  
          }  
      
          switch (e.Key)  
          {  
            case Key.Down  
              when comboBox.Items.CurrentPosition < comboBox.Items.Count - 1  
                   && comboBox.Items.MoveCurrentToNext():  
            case Key.Up  
              when comboBox.Items.CurrentPosition > 0  
                   && comboBox.Items.MoveCurrentToPrevious():  
              {  
                MyComboBox.IsNavigationKeyPressed = true;  
      
                await Application.Current.Dispatcher.InvokeAsync(  
                  () =>  
                  {  
                    MyComboBox.SelectCurrentItem(textBox, comboBox);  
                    MyComboBox.IsNavigationKeyPressed = false;  
                  },  
                  DispatcherPriority.ContextIdle);  
      
                break;  
              }  
          }  
        }  
        private static void SelectCurrentItem(TextBox textBox, ComboBox comboBox)  
        {  
          comboBox.SelectedItem = comboBox.Items.CurrentItem;  
          if (MyComboBox.TextBoxSelectionStartMap.TryGetValue(textBox, out int selectionStart))  
          {  
            textBox.SelectionStart = selectionStart;  
          }  
        }  
        private static void EnableAutocompleteFilterOnComboBoxLoaded(object sender, RoutedEventArgs e)  
        {  
          var comboBox = sender as ComboBox;  
          MyComboBox.EnableAutocompleteFilter(comboBox);  
        }  
        private static void EnableAutocompleteFilter(ComboBox comboBox)  
        {  
          if (comboBox.TryFindVisualChildElement(out TextBox editTextBox))  
          {  
            MyComboBox.TextBoxComboBoxMap.Add(editTextBox, comboBox);  
            MyComboBox.ComboBoxTextBoxMap.Add(comboBox, editTextBox);  
            editTextBox.TextChanged += MyComboBox.FilterOnTextInput;  
            comboBox.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler(HandleKeyDownWhileFiltering), true);  
          }  
        }  
        private static void DisableAutocompleteFilter(ComboBox comboBox)  
        {  
          if (comboBox.TryFindVisualChildElement(out TextBox editTextBox))  
          {  
            MyComboBox.TextBoxComboBoxMap.Remove(editTextBox);  
            editTextBox.TextChanged -= MyComboBox.FilterOnTextInput;  
          }  
        }  
      }  
    }  
      
    

    The result:
    200894-1.gif


    If the response is helpful, please click "Accept Answer" and upvote it.
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    2 people found this answer helpful.

  2. Sarah 186 Reputation points
    2022-05-31T10:17:50.143+00:00

    Hi @Hui Liu-MSFT , thx. How can I integrate the function into the above code (MVVM)?