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,783 questions
{count} votes

2 answers

Sort by: Most helpful
  1. Hui Liu-MSFT 48,571 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.

    4 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)?


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.