create autocomplete feature

fatih uyanık 140 Reputation points
2023-05-24T08:11:33.8266667+00:00

Hello

While developing a Win form application, we were able to create an auto-complete feature for combo boxes and text boxes. In other words, when the user started to enter values, it was possible to choose from the suggestions.

Can we do the same application in wpf applications? In other words, when the user enters a value, can we offer suggestions from the data stored in the database?

Thanks.

C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
11,111 questions
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. Hui Liu-MSFT 48,586 Reputation points Microsoft Vendor
    2023-05-24T10:12:21.61+00:00

    Hi,@fatih uyanık. Welcome Microsoft Q&A.

    For create an auto-complete feature for ComboBox in wpf, you could refer to the following solution.

    Xaml:

     <Window.Resources>
            <ControlTemplate  
                 x:Key="ComboBoxToggleButton"  
                 TargetType="{x:Type ToggleButton}">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition />
                        <ColumnDefinition  
                             Width="20" />
                    </Grid.ColumnDefinitions>
                    <Border  
                         x:Name="Border"  
                         Grid.ColumnSpan="2"  
                         CornerRadius="0"  
                         Background="#FF3F3F3F"  
                         BorderBrush="#FF97A0A5"  
                         BorderThickness="1" />
                    <Border  
                         Grid.Column="0"  
                         CornerRadius="0"  
                         Margin="1"  
                         Background="#FF3F3F3F"  
                         BorderBrush="#FF97A0A5"  
                         BorderThickness="0,0,1,0" />
                    <Path  
                         x:Name="Arrow"  
                         Grid.Column="1"  
                         Fill="White"  
                         HorizontalAlignment="Center"  
                         VerticalAlignment="Center"  
                         Data="M0,0 L0,2 L4,6 L8,2 L8,0 L4,4 z" />
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger  
                         Property="ToggleButton.IsMouseOver"  
                         Value="true">
                        <Setter  
                             TargetName="Border"  
                             Property="Background"  
                             Value="#808080" />
                    </Trigger>
                    <Trigger  
                         Property="ToggleButton.IsChecked"  
                         Value="true">
                        <Setter  
                             TargetName="Border"  
                             Property="Background"  
                             Value="#E0E0E0" />
                    </Trigger>
                    <Trigger  
                         Property="IsEnabled"  
                         Value="False">
                        <Setter  
                             TargetName="Border"  
                             Property="Background"  
                             Value="#EEEEEE" />
                        <Setter  
                             TargetName="Border"  
                             Property="BorderBrush"  
                             Value="#AAAAAA" />
                        <Setter  
                             Property="Foreground"  
                             Value="#888888" />
                        <Setter  
                             TargetName="Arrow"  
                             Property="Fill"  
                             Value="#888888" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
            <ControlTemplate  
                 x:Key="ComboBoxTextBox"  
                 TargetType="{x:Type TextBox}">
                <Border  
                     x:Name="PART_ContentHost"  
                     Focusable="False"  
                     Background="{TemplateBinding Background}" />
            </ControlTemplate>
            <Style  
                 x:Key="{x:Type ComboBox}"  
                 TargetType="{x:Type ComboBox}">
                <Setter  
                     Property="SnapsToDevicePixels"  
                     Value="true" />
                <Setter  
                     Property="OverridesDefaultStyle"  
                     Value="true" />
                <Setter  
                     Property="ScrollViewer.HorizontalScrollBarVisibility"  
                     Value="Auto" />
                <Setter  
                     Property="ScrollViewer.VerticalScrollBarVisibility"  
                     Value="Auto" />
                <Setter  
                     Property="ScrollViewer.CanContentScroll"  
                     Value="true" />
                <Setter  
                     Property="MinWidth"  
                     Value="120" />
                <Setter  
                     Property="MinHeight"  
                     Value="20" />
                <Setter  
                     Property="Foreground"  
                     Value="White" />
                <Setter  
                     Property="Template">
                    <Setter.Value>
                        <ControlTemplate  
                             TargetType="{x:Type ComboBox}">
                            <Grid>
                                <ToggleButton  
                                     Name="ToggleButton"  
                                     Template="{StaticResource ComboBoxToggleButton}"  
                                     Grid.Column="2"  
                                     Focusable="false"  
                                     IsChecked="{Binding Path=IsDropDownOpen,Mode=TwoWay,RelativeSource={RelativeSource TemplatedParent}}"  
                                     ClickMode="Press"></ToggleButton>
                                <ContentPresenter  
                                     Name="ContentSite"  
                                     IsHitTestVisible="False"  
                                     Content="{TemplateBinding SelectionBoxItem}"  
                                     ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"  
                                     ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"  
                                     Margin="3,3,23,3"  
                                     VerticalAlignment="Center"  
                                     HorizontalAlignment="Left" />
                                <TextBox  
                                     x:Name="PART_EditableTextBox"  
                                     Style="{x:Null}"  
                                     Template="{StaticResource ComboBoxTextBox}"  
                                     HorizontalAlignment="Left"  
                                     VerticalAlignment="Center"  
                                     Margin="3,3,23,3"  
                                     Focusable="True"  
                                     Background="#FF3F3F3F"  
                                     Foreground="Green"  
                                     Visibility="Hidden"  
                                     IsReadOnly="{TemplateBinding IsReadOnly}" />
                                <Popup  
                                     Name="Popup"  
                                     Placement="Bottom"  
                                     IsOpen="{TemplateBinding IsDropDownOpen}"  
                                     AllowsTransparency="True"  
                                     Focusable="False"  
                                     PopupAnimation="Slide">
                                    <Grid  
                                         Name="DropDown"  
                                         SnapsToDevicePixels="True"  
                                         MinWidth="{TemplateBinding ActualWidth}"  
                                         MaxHeight="{TemplateBinding MaxDropDownHeight}">
                                        <Border  
                                             x:Name="DropDownBorder"  
                                             Background="#3f3f3f"  
                                             BorderThickness="1"  
                                             BorderBrush="#888888" />
                                        <ScrollViewer  
                                             Margin="4,6,4,6"  
                                             SnapsToDevicePixels="True">
                                            <StackPanel  
                                                 IsItemsHost="True"  
                                                 KeyboardNavigation.DirectionalNavigation="Contained" />
                                        </ScrollViewer>
                                    </Grid>
                                </Popup>
                            </Grid>
                            <ControlTemplate.Triggers>
                                <Trigger  
                                     Property="HasItems"  
                                     Value="false">
                                    <Setter  
                                         TargetName="DropDownBorder"  
                                         Property="MinHeight"  
                                         Value="95" />
                                </Trigger>
                                <Trigger  
                                     Property="IsEnabled"  
                                     Value="false">
                                    <Setter  
                                         Property="Foreground"  
                                         Value="#888888" />
                                </Trigger>
                                <Trigger  
                                     Property="IsGrouping"  
                                     Value="true">
                                    <Setter  
                                         Property="ScrollViewer.CanContentScroll"  
                                         Value="false" />
                                </Trigger>
                                <Trigger  
                                     SourceName="Popup"  
                                     Property="Popup.AllowsTransparency"  
                                     Value="true">
                                    <Setter  
                                         TargetName="DropDownBorder"  
                                         Property="CornerRadius"  
                                         Value="0" />
                                    <Setter  
                                         TargetName="DropDownBorder"  
                                         Property="Margin"  
                                         Value="0,2,0,0" />
                                </Trigger>
                                <Trigger  
                                     Property="IsEditable"  
                                     Value="true">
                                    <Setter  
                                         Property="IsTabStop"  
                                         Value="false" />
                                    <Setter  
                                         TargetName="PART_EditableTextBox"  
                                         Property="Visibility"  
                                         Value="Visible" />
                                    <Setter  
                                         TargetName="ContentSite"  
                                         Property="Visibility"  
                                         Value="Hidden" />
                                </Trigger>
                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
    
            <Style  
                 x:Key="{x:Type ComboBoxItem}"  
                 TargetType="{x:Type ComboBoxItem}">
                <Setter  
                     Property="SnapsToDevicePixels"  
                     Value="true" />
                <Setter  
                     Property="Foreground"  
                     Value="#AAF2D1" />
                <Setter  
                     Property="OverridesDefaultStyle"  
                     Value="true" />
                <Setter  
                     Property="FontSize"  
                     Value="15" />
                <Setter  
                     Property="Template">
                    <Setter.Value>
                        <ControlTemplate  
                             TargetType="{x:Type ComboBoxItem}">
                            <Border  
                                 Name="Border"  
                                 Padding="2"  
                                 SnapsToDevicePixels="true">
                                <ContentPresenter />
                            </Border>
                            <ControlTemplate.Triggers>
                                <Trigger  
                                     Property="IsHighlighted"  
                                     Value="true">
                                    <Setter  
                                         TargetName="Border"  
                                         Property="Background"  
                                         Value="#5A3BD7" />
                                    <Setter  
                                         TargetName="Border"  
                                         Property="Opacity"  
                                         Value="0.4" />
                                    <Setter  
                                         Property="Foreground"  
                                         Value="#fcba03" />
                                    <Setter  
                                         Property="FontSize"  
                                         Value="16" />
                                    <Setter  
                                         Property="FontWeight"  
                                         Value="DemiBold" />
                                </Trigger>
                                <Trigger  
                                     Property="IsEnabled"  
                                     Value="false">
                                    <Setter  
                                         Property="Foreground"  
                                         Value="#888888" />
                                </Trigger>
                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
    
    
    
    
        </Window.Resources>
        <Window.DataContext>
            <local:ViewModel/>
        </Window.DataContext>
        <StackPanel>
            <ComboBox  
                         Name="cmb" IsTextSearchEnabled="True" IsEditable="True"  
                         Grid.Column="0"  ItemsSource="{Binding Countries}" 
                         HorizontalAlignment="Left"   
                     
                         VerticalAlignment="Top"  
                         FontFamily="Segoe UI"  
                         FontStyle="Normal"  
                         FontSize="16"  
                         Margin="10 10 10 0"  
                         Height="35"  
                         MaxHeight="40" local:MyComboBox.IsFilterOnAutocompleteEnabled="True"  
                         Width="350"  
                         MaxWidth="450" />
    </StackPanel>
    
    

    Codebedhind:

     public class ViewModel : INotifyPropertyChanged
        {
          
            static String connectionString = @"connstring";
            SqlConnection con;
            SqlCommand cmd;
            SqlDataAdapter adapter;
            DataSet ds;
      
            public String txtSelectedItem { get; set; }
            private ObservableCollection<Country> countries;
            public ObservableCollection<Country> Countries
            {
                get
                {
                    return countries;
                }
                set { countries = value; OnPropertyChanged("Countries"); }
            }
            public void FillList()
            {
                try
                {
                    con = new SqlConnection(connectionString);
                    con.Open();
                    cmd = new SqlCommand("select * from [dbo].[Country]", con);
                    adapter = new SqlDataAdapter(cmd);
                    ds = new DataSet();
                    adapter.Fill(ds, "tblCountries");
    
                    if (Countries == null)
                        Countries = new ObservableCollection<Country>();
    
                    foreach (DataRow dr in ds.Tables[0].Rows)
                    {
                        Countries.Add(new Country
                        {
                            CountryId = Convert.ToInt32(dr[0].ToString()),
                            CountryName = dr[1].ToString(),
    
                        });
                    }
                }
                catch (Exception ex)
                {
    
                }
                finally
                {
                    ds = null;
                    adapter.Dispose();
                    con.Close();
                    con.Dispose();
                }
            }
          
            public ViewModel()
            {
                Countries = new ObservableCollection<Country>();
    
                txtSelectedItem = "Please select a country";
                FillList();
               
            }
            public event PropertyChangedEventHandler PropertyChanged;
            internal void OnPropertyChanged([CallerMemberName] string propName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
        }
    
        public class Country
        {
            public int CountryId { get; set; }
            public string CountryName { get; set; }
            public override string ToString()
            {
                return CountryName;
            }
        }
    
        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;
                }
            }
        }
    
        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;
            }
        }
    
    
    
    

    The result:

    1


    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.


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.