I was always curious about a multi-column AutoComplete textbox with header, and especially an autocomplete textbox in which an AutoComplete Popup list can be placed anywhere as per our wish. So, I have developed an AutoComplete textbox as per my needs (In WPF, C#). You can have look at it in the below image.
AutoComplete List displays at the bottom of control. (without style),
AutoComplete List displays at the right side of window. (without style),
AutoComplete List displays at bottom of control. (with style),
AutoComplete List Displays at right side of window. (with style),
How to create AutoComplete TextBox in WPF
Create A WPF App project
(Open Visual Studio -> File -> New -> Project -> Select “WPF App” -> Type name as “WPFControls” – Here I have used Visual Studio 2017)
Create a directory (Namespace) under project as “Controls” and add a class “AutoCompleteTextBox.cs” in that directory.
Now, In “AutoCompleteTextBox.cs” we are writing our logic to create AutoCompleteTextBox.
Here we have to Inherit Control class in AutoCompleteTextBox to get properties, events, and methods of “Control” class.
Now, Declare custom properties (DependencyProperty) for our AutoCompleteTextBox. As follows.
- public static readonly DependencyProperty AutoCompleteColumnsProperty = DependencyProperty.Register("AutoCompleteColumns", typeof(ObservableCollection<DataGridColumn>), typeof(AutoCompleteTextBox));
-
- public ObservableCollection<DataGridColumn> AutoCompleteColumns
- {
- get
- {
- return (ObservableCollection<DataGridColumn>)GetValue(AutoCompleteColumnsProperty);
- }
- set
- {
- SetValue(AutoCompleteColumnsProperty, value);
- }
- }
The property “AutoCompleteColumns” contains the column for AutoComplete List. This allows the programmer to define columns from design (xaml) code.
- public static readonly DependencyProperty AutoCompleteItemSourceProperty = DependencyProperty.Register("AutoCompleteItemSource", typeof(IEnumerable<object>), typeof(AutoCompleteTextBox));
-
- public IEnumerable<object> AutoCompleteItemSource
- {
- get
- {
- return (IEnumerable<object>)GetValue(AutoCompleteItemSourceProperty);
- }
- set
- {
- SetValue(AutoCompleteItemSourceProperty, value);
- }
- }
“AutoCompleteItemSource” is a collection (IEnumerable<object>) to hold the Items for AutoCompleteList.
- public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(object), typeof(AutoCompleteTextBox));
-
- public object SelectedItem
- {
- get
- {
- return GetValue(SelectedItemProperty);
- }
- set
- {
- SetValue(SelectedItemProperty, value);
-
- if (value != null)
- {
- TXT_SEARCHINPUT.Text = value.ToString();
- TXT_SEARCHINPUT.Select(TXT_SEARCHINPUT.Text.Length, 0);
- }
-
- if (this.OnSelectedItemChange != null)
- this.OnSelectedItemChange.Invoke(this, new EventArgs());
- }
- }
“SelectedItem” is used for Item selected from AutoComplete List by user.
- public static readonly DependencyProperty AutoCompletePlacementDependencyProperty = DependencyProperty.Register("AutoCompletePlacement", typeof(PlacementMode), typeof(AutoCompleteTextBox));
-
- public PlacementMode AutoCompletePlacement
- {
- get
- {
- return (PlacementMode)GetValue(AutoCompletePlacementDependencyProperty);
- }
- set
- {
- SetValue(AutoCompletePlacementDependencyProperty, value);
- }
- }
“AutoCompletePlacement” property allows the programmer to define AutoCompleteList Popup placement (for eg. Left, Right, Bottom, Absolute)
- public static readonly DependencyProperty AutoCompletePlacementTargetDependencyProperty = DependencyProperty.Register("AutoCompletePlacementTarget", typeof(UIElement), typeof(AutoCompleteTextBox));
-
- public UIElement AutoCompletePlacementTarget
- {
- get
- {
- return (UIElement)GetValue(AutoCompletePlacementTargetDependencyProperty);
- }
- set
- {
- SetValue(AutoCompletePlacementTargetDependencyProperty, value);
- }
- }
“AutoCompletePlacementTarget” defines the control related to AutoComplete Popup placement.
- public static readonly DependencyProperty AutoCompleteHorizontalOffsetDependencyProperty = DependencyProperty.Register("AutoCompleteHorizontalOffset", typeof(double), typeof(AutoCompleteTextBox));
-
- public double AutoCompleteHorizontalOffset
- {
- get { return (double)GetValue(AutoCompleteHorizontalOffsetDependencyProperty); }
- set { SetValue(AutoCompleteHorizontalOffsetDependencyProperty, value); }
- }
-
- public static readonly DependencyProperty AutoCompleteVerticalOffsetDependencyProperty = DependencyProperty.Register("AutoCompleteVerticalOffset", typeof(double), typeof(AutoCompleteTextBox));
-
- public double AutoCompleteVerticalOffset
- {
- get { return (double)GetValue(AutoCompleteVerticalOffsetDependencyProperty); }
- set { SetValue(AutoCompleteVerticalOffsetDependencyProperty, value); }
- }
“AutoCompleteHorizontalOffset” and “AutoCompleteVerticalOffset” allows you to set horizontal/vertical offset of AutoCompleteList Popup.
- public static readonly DependencyProperty AutoCompleteWidthDependencyProperty = DependencyProperty.Register("AutoCompleteWidth", typeof(double), typeof(AutoCompleteTextBox));
-
- public double AutoCompleteWidth
- {
- get { return (double)GetValue(AutoCompleteWidthDependencyProperty); }
- set
- {
- SetValue(AutoCompleteWidthDependencyProperty, value);
- }
- }
-
-
-
- public static readonly DependencyProperty AutoCompleteHeightDependencyProperty = DependencyProperty.Register("AutoCompleteHeight", typeof(double), typeof(AutoCompleteTextBox));
-
- public double AutoCompleteHeight
- {
- get { return (double)GetValue(AutoCompleteHeightDependencyProperty); }
- set
- {
- SetValue(AutoCompleteHeightDependencyProperty, value);
- }
- }
“AutoCompleteWidth” and “AutoCompleteHeight” define the width and height of AutoComplete Popup.
Define Controls and Events Used For AutoCompleteTextBox,
- private TextBox TXT_SEARCHINPUT;
- private Popup PUP_AC;
- private DataGrid DG_AC;
-
-
-
- public event TextChangedEventHandler OnTextChange;
-
- public event EventHandler OnSelectedItemChange;
-
- public AutoCompleteTextBox()
- {
- AutoCompleteColumns = new ObservableCollection<DataGridColumn>();
- }
-
-
- public override void OnApplyTemplate()
- {
- base.OnApplyTemplate();
-
- TXT_SEARCHINPUT = this.Template.FindName("TXT_SEARCHINPUT", this) as TextBox;
- TXT_SEARCHINPUT.TextChanged += TXT_SEARCHINPUT_TextChanged;
- TXT_SEARCHINPUT.PreviewKeyDown += TXT_SEARCHINPUT_PreviewKeyDown;
-
- PUP_AC = this.Template.FindName("PUP_AC", this) as Popup;
-
- DG_AC = this.Template.FindName("DG_AC", this) as DataGrid;
- DG_AC.MouseLeftButtonUp += DG_AC_MouseLeftButtonUp;
- foreach (DataGridColumn column in AutoCompleteColumns)
- DG_AC.Columns.Add(column);
- }
“OnApplyTemplate()” is used to find and initialize controls from ControlTemplate defined in “AutoCompleteTextBox.xaml”.
-
- private void DG_AC_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
- {
- var item = DG_AC.SelectedItem;
- if (item == null)
- {
- this.SelectedItem = null;
- return;
- }
-
- if (PUP_AC.IsOpen)
- {
- this.SelectedItem = item;
- PUP_AC.IsOpen = false;
- }
- }
-
-
-
- private void TXT_SEARCHINPUT_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
- {
- if (e.Key == System.Windows.Input.Key.Down)
- {
- if (DG_AC.Items.Count > 0)
- {
- int SelectedIndex = DG_AC.SelectedIndex;
- if (SelectedIndex < DG_AC.Items.Count)
- DG_AC.SelectedIndex++;
- }
- }
- else if (e.Key == System.Windows.Input.Key.Up)
- {
- if (DG_AC.Items.Count > 0)
- {
- int SelectedIndex = DG_AC.SelectedIndex;
- if (SelectedIndex > 0)
- DG_AC.SelectedIndex--;
- }
- }
- else if (e.Key == System.Windows.Input.Key.Enter)
- {
- var item = DG_AC.SelectedItem;
- if (item == null)
- {
- this.SelectedItem = null;
- return;
- }
-
- if (PUP_AC.IsOpen)
- {
- this.SelectedItem = item;
- PUP_AC.IsOpen = false;
- }
-
- }
- }
-
-
- private void TXT_SEARCHINPUT_TextChanged(object sender, TextChangedEventArgs e)
- {
- if (SelectedItem != null && SelectedItem.ToString() != TXT_SEARCHINPUT.Text)
- this.SelectedItem = null;
-
- if (string.IsNullOrEmpty(TXT_SEARCHINPUT.Text))
- {
- PUP_AC.IsOpen = false;
- }
- else
- {
- PUP_AC.IsOpen = true;
- }
-
- if (this.OnTextChange != null)
- this.OnTextChange.Invoke(sender, e);
- }
The complete code of AutoCompleteTextBox.cs will look like:
Design ControlTemplate For AutoCompleteTextBox In “AutoCompleteTextBox.xaml”
(Create a directory under project as “Resources” and add ResourceDictionary named “AutoCompleteTextBox.xaml” and write the following code in it.) as shown in fig.
- <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:controls="clr-namespace:WPFControls.Controls" >
-
- <Style TargetType="{x:Type controls:AutoCompleteTextBox}">
- <Setter Property="Template">
- <Setter.Value>
- <ControlTemplate TargetType="{x:Type controls:AutoCompleteTextBox}">
- <Grid>
- <Grid.RowDefinitions>
- <RowDefinition Height="*"></RowDefinition>
- </Grid.RowDefinitions>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="*"></ColumnDefinition>
- </Grid.ColumnDefinitions>
-
-
- <TextBox x:Name="TXT_SEARCHINPUT" Grid.Row="0" Grid.Column="0"></TextBox>
-
- <Popup x:Name="PUP_AC"
- StaysOpen="False"
- Placement="{Binding Path=AutoCompletePlacement, RelativeSource={RelativeSource TemplatedParent}}"
- PlacementTarget="{Binding Path=AutoCompletePlacementTarget, RelativeSource={RelativeSource TemplatedParent}}"
- HorizontalOffset="{Binding Path=AutoCompleteHorizontalOffset, RelativeSource={RelativeSource TemplatedParent}}"
- VerticalOffset="{Binding Path=AutoCompleteVerticalOffset, RelativeSource={RelativeSource TemplatedParent}}"
- >
- <Grid>
- <Grid.RowDefinitions>
- <RowDefinition Height="*"></RowDefinition>
- </Grid.RowDefinitions>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="*"></ColumnDefinition>
- </Grid.ColumnDefinitions>
- <Border x:Name="PUP_BDR" Grid.Row="0" Grid.Column="0"
- BorderThickness="1" BorderBrush="#FFF4F4F4" Background="#FFFCFCFC" >
- </Border>
-
-
- <DataGrid x:Name="DG_AC" Grid.Row="0" Grid.Column="0"
- Width="{Binding Path=AutoCompleteWidth, RelativeSource={RelativeSource TemplatedParent}}"
- Height="{Binding Path=AutoCompleteHeight, RelativeSource={RelativeSource TemplatedParent}}"
- AutoGenerateColumns="False"
- HeadersVisibility="Column"
- CanUserAddRows="False" HorizontalAlignment="Stretch"
- ItemsSource="{TemplateBinding AutoCompleteItemSource}"
- SelectionMode="Single"
- SelectionUnit="FullRow"
- >
- </DataGrid>
- </Grid>
- </Popup>
- </Grid>
- </ControlTemplate>
- </Setter.Value>
- </Setter>
- </Style>
- </ResourceDictionary>
Now, our AutoCompleteTextBox is ready to use.
Step 1
Add/Merge “AutoCompleteTextBox.xaml” in “App.xaml” in project.
- <Application x:Class="WPFControls.App"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:local="clr-namespace:WPFControls"
- StartupUri="MainWindow.xaml">
- <Application.Resources>
- <ResourceDictionary>
- <ResourceDictionary.MergedDictionaries>
- <ResourceDictionary Source="Resources/AutoCompleteTextBox.xaml"></ResourceDictionary>
- </ResourceDictionary.MergedDictionaries>
- </ResourceDictionary>
- </Application.Resources>
- </Application>
Step 2
Add AutoCompleteTextBox where you want (MainWindow.xaml) as follows.
AutoComplete Placement At Bottom Of Input Control,
- <TextBlock Name="tb" Text="Selcted Employee : "></TextBlock>
-
-
- <controls:AutoCompleteTextBox x:Name="txt"
- AutoCompleteWidth="{Binding ElementName=txt, Path=ActualWidth}"
- AutoCompleteHeight="150"
- AutoCompletePlacementTarget="{Binding ElementName=txt}"
- AutoCompletePlacement="Bottom"
- OnTextChange="Txt_OnTextChange"
- OnSelectedItemChange="Txt_OnSelectedItemChange"
- >
- <controls:AutoCompleteTextBox.AutoCompleteColumns>
- <DataGridTextColumn Header="Id" MinWidth="60" Width="Auto" Binding="{Binding EmployeeId}"></DataGridTextColumn>
- <DataGridTextColumn Header="Full Name" Width="*" Binding="{Binding FullName}"></DataGridTextColumn>
- <DataGridTextColumn Header="Contact" Width="150" Binding="{Binding Contact}"></DataGridTextColumn>
- <DataGridTextColumn Header="Salary" Width="100" Binding="{Binding Salary}"></DataGridTextColumn>
- </controls:AutoCompleteTextBox.AutoCompleteColumns>
- </controls:AutoCompleteTextBox>
AutoComplete Placement at the right side of Window/Panel,
- <TextBlock Name="tb2" Text="Selcted Employee : " Margin="0 20 0 0"></TextBlock>
-
-
- <controls:AutoCompleteTextBox x:Name="txt2"
- Width="300"
- HorizontalAlignment="Left"
- AutoCompleteWidth="430"
- AutoCompleteHorizontalOffset="-430"
- AutoCompleteHeight="{Binding ElementName=pnl, Path=ActualHeight}"
- AutoCompletePlacementTarget="{Binding ElementName=pnl}"
- AutoCompletePlacement="Right"
- OnTextChange="Txt2_OnTextChange"
- OnSelectedItemChange="Txt2_OnSelectedItemChange"
- >
- <controls:AutoCompleteTextBox.AutoCompleteColumns>
- <DataGridTextColumn Header="Id" MinWidth="40" Width="Auto" Binding="{Binding EmployeeId}"></DataGridTextColumn>
- <DataGridTextColumn Header="Full Name" Width="*" Binding="{Binding FullName}"></DataGridTextColumn>
- <DataGridTextColumn Header="Contact" Width="Auto" Binding="{Binding Contact}"></DataGridTextColumn>
- <DataGridTextColumn Header="Salary" Width="Auto" Binding="{Binding Salary}"></DataGridTextColumn>
- </controls:AutoCompleteTextBox.AutoCompleteColumns>
- </controls:AutoCompleteTextBox>
Complete code of “MainWindow.xaml”
- <Window x:Class="WPFControls.MainWindow"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:local="clr-namespace:WPFControls"
- xmlns:controls="clr-namespace:WPFControls.Controls"
- mc:Ignorable="d"
- Title="MainWindow" Height="450" Width="800" FontSize="14">
- <StackPanel Name="pnl" Margin="20">
- <TextBlock Name="tb" Text="Selcted Employee : "></TextBlock>
-
-
- <controls:AutoCompleteTextBox x:Name="txt"
- AutoCompleteWidth="{Binding ElementName=txt, Path=ActualWidth}"
- AutoCompleteHeight="150"
- AutoCompletePlacementTarget="{Binding ElementName=txt}"
- AutoCompletePlacement="Bottom"
- OnTextChange="Txt_OnTextChange"
- OnSelectedItemChange="Txt_OnSelectedItemChange"
- >
- <controls:AutoCompleteTextBox.AutoCompleteColumns>
- <DataGridTextColumn Header="Id" MinWidth="60" Width="Auto" Binding="{Binding EmployeeId}"></DataGridTextColumn>
- <DataGridTextColumn Header="Full Name" Width="*" Binding="{Binding FullName}"></DataGridTextColumn>
- <DataGridTextColumn Header="Contact" Width="150" Binding="{Binding Contact}"></DataGridTextColumn>
- <DataGridTextColumn Header="Salary" Width="100" Binding="{Binding Salary}"></DataGridTextColumn>
- </controls:AutoCompleteTextBox.AutoCompleteColumns>
- </controls:AutoCompleteTextBox>
-
- <TextBlock Name="tb2" Text="Selcted Employee : " Margin="0 20 0 0"></TextBlock>
-
-
- <controls:AutoCompleteTextBox x:Name="txt2"
- Width="300"
- HorizontalAlignment="Left"
- AutoCompleteWidth="430"
- AutoCompleteHorizontalOffset="-430"
- AutoCompleteHeight="{Binding ElementName=pnl, Path=ActualHeight}"
- AutoCompletePlacementTarget="{Binding ElementName=pnl}"
- AutoCompletePlacement="Right"
- OnTextChange="Txt2_OnTextChange"
- OnSelectedItemChange="Txt2_OnSelectedItemChange"
- >
- <controls:AutoCompleteTextBox.AutoCompleteColumns>
- <DataGridTextColumn Header="Id" MinWidth="40" Width="Auto" Binding="{Binding EmployeeId}"></DataGridTextColumn>
- <DataGridTextColumn Header="Full Name" Width="*" Binding="{Binding FullName}"></DataGridTextColumn>
- <DataGridTextColumn Header="Contact" Width="Auto" Binding="{Binding Contact}"></DataGridTextColumn>
- <DataGridTextColumn Header="Salary" Width="Auto" Binding="{Binding Salary}"></DataGridTextColumn>
- </controls:AutoCompleteTextBox.AutoCompleteColumns>
- </controls:AutoCompleteTextBox>
- </StackPanel>
- </Window>
Now handle OnTextChange and OnSelectedItemChange events of AutoCompleteTextBox in “MainWindow.xaml.cs” (Here we have used an Employee class to demonstrate the working of AutoCompleteTextBox, so create a class “Employee.cs” as follows),
- public class Employee
- {
- public int EmployeeId { get; set; }
- public string FullName { get; set; }
- public string Contact { get; set; }
- public double Salary { get; set; }
-
- public override string ToString()
- {
- return string.Format("{0}", FullName);
- }
- }
Declare a list of Employees for AutoCompleteItemSource,
- public List<Employee> Employees;
- public MainWindow()
- {
- InitializeComponent();
- Employees = new List<Employee>();
- Employees.Add(new Employee() { EmployeeId = 1, FullName = "Pradnya Prakash Dabhade", Contact = "8806558104", Salary = 75000 });
- Employees.Add(new Employee() { EmployeeId = 2, FullName = "Sudhanshoo Dnyaneshwar Wadekar", Contact = "9284111845", Salary = 43000 });
- Employees.Add(new Employee() { EmployeeId = 3, FullName = "Amit Pravin Chavan", Contact = "9545812505", Salary = 58000 });
- Employees.Add(new Employee() { EmployeeId = 4, FullName = "Aayush Prakash Dabhade", Contact = "8806558104", Salary = 43000 });
- Employees.Add(new Employee() { EmployeeId = 5, FullName = "Shashank Dnyaneshwar Wadekar", Contact = "9284111845", Salary = 60000 });
- Employees.Add(new Employee() { EmployeeId = 6, FullName = "Pooja Dnyaneshwar Wadekar", Contact = "9284111845", Salary = 40000 });
-
- }
Handle events to filter record and selected item,
- private void Txt_OnTextChange(object sender, TextChangedEventArgs e)
- {
- TextBox txtInput = sender as TextBox;
- var Emps = from emp in Employees where emp.FullName.Contains(txtInput.Text) select emp;
- txt.AutoCompleteItemSource = Emps;
-
- }
-
- private void Txt_OnSelectedItemChange(object sender, EventArgs e)
- {
- if (txt.SelectedItem == null)
- tb.Text = "Selcted Employee :";
- else
- {
- tb.Text = "Selcted Employee : " + txt.SelectedItem.ToString();
- }
- }
-
- private void Txt2_OnTextChange(object sender, TextChangedEventArgs e)
- {
- TextBox txtInput = sender as TextBox;
- var Emps = from emp in Employees where emp.FullName.Contains(txtInput.Text) select emp;
- txt2.AutoCompleteItemSource = Emps;
-
- }
-
- private void Txt2_OnSelectedItemChange(object sender, EventArgs e)
- {
- if (txt2.SelectedItem == null)
- tb2.Text = "Selcted Employee :";
- else
- {
- tb2.Text = "Selcted Employee : " + txt2.SelectedItem.ToString();
- }
- }
Complete code of “MainWindow.xaml.cs”
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Windows;
- using System.Windows.Controls;
-
- namespace WPFControls
- {
- public partial class MainWindow : Window
- {
- public List<Employee> Employees;
- public MainWindow()
- {
- InitializeComponent();
- Employees = new List<Employee>();
- Employees.Add(new Employee() { EmployeeId = 1, FullName = "Pradnya Prakash Dabhade", Contact = "8806558104", Salary = 75000 });
- Employees.Add(new Employee() { EmployeeId = 2, FullName = "Sudhanshoo Dnyaneshwar Wadekar", Contact = "9284111845", Salary = 43000 });
- Employees.Add(new Employee() { EmployeeId = 3, FullName = "Amit Pravin Chavan", Contact = "9545812505", Salary = 58000 });
- Employees.Add(new Employee() { EmployeeId = 4, FullName = "Aayush Prakash Dabhade", Contact = "8806558104", Salary = 43000 });
- Employees.Add(new Employee() { EmployeeId = 5, FullName = "Shashank Dnyaneshwar Wadekar", Contact = "9284111845", Salary = 60000 });
- Employees.Add(new Employee() { EmployeeId = 6, FullName = "Pooja Dnyaneshwar Wadekar", Contact = "9284111845", Salary = 40000 });
-
- }
-
- private void Txt_OnTextChange(object sender, TextChangedEventArgs e)
- {
- TextBox txtInput = sender as TextBox;
- var Emps = from emp in Employees where emp.FullName.Contains(txtInput.Text) select emp;
- txt.AutoCompleteItemSource = Emps;
-
- }
-
- private void Txt_OnSelectedItemChange(object sender, EventArgs e)
- {
- if (txt.SelectedItem == null)
- tb.Text = "Selcted Employee :";
- else
- {
- tb.Text = "Selcted Employee : " + txt.SelectedItem.ToString();
- }
- }
-
- private void Txt2_OnTextChange(object sender, TextChangedEventArgs e)
- {
- TextBox txtInput = sender as TextBox;
- var Emps = from emp in Employees where emp.FullName.Contains(txtInput.Text) select emp;
- txt2.AutoCompleteItemSource = Emps;
-
- }
-
- private void Txt2_OnSelectedItemChange(object sender, EventArgs e)
- {
- if (txt2.SelectedItem == null)
- tb2.Text = "Selcted Employee :";
- else
- {
- tb2.Text = "Selcted Employee : " + txt2.SelectedItem.ToString();
- }
- }
- }
- }
Now we are ready to run our project.
You can design the suggestion style as per your requirement.
Please download the attached project for the proper code/demo.