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:
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.