Reputation: 970
I need an autocomplete combobox for WPF C#. I've tried several approaches but nothing works. For example I've tried a combobox:
<ComboBox Width="200"
IsEditable="True"
ItemsSource="{Binding Names}"
IsTextSearchEnabled="True"
HorizontalAlignment="Left"/>
Names
is a List of Strings: Peter John, John, John Doe, Cathy, Howard, John Richards and so on
If you type in a name e.g. John the combobox should expand and I should see
But that doesn't work. How can I do that?
Upvotes: 13
Views: 53556
Reputation: 1593
Since the link for autocomplete control mentioned in a previous answer is no longer active and in my humble opinion using predefined control is a more viable approach than reinventing your own wheel, here's the link for nice control. You install it as follows
Install-Package AutoCompleteTextBox
Here's a demo on how to use it in your code.
Upvotes: 1
Reputation: 314
Use ComboBox.Items.Filter to show items that fits the text written in the textbox. This is an example:
If cmb.Text = "" Then
cmb.Items.Filter = Nothing
Else
Dim T = cmb.Text
cmb.Items.Filter = Nothing
Dim I = cmb.Items.IndexOf(T)
cmb.Items.Filter = Function(x As String)
If x.StartsWith(T) Then Return True
If x.Contains(" " & T) Then Return True
Return False
End Function
If I = -1 Then
cmb.SelectedIndex = -1
cmb.Text = T
Dim txt As TextBox = cmb.Template.FindName("PART_EditableTextBox", cmb)
txt.SelectionStart = T.Length
Else
cmb.SelectedIndex = 0
End If
End If
Upvotes: 0
Reputation: 2063
Here is the implementation that works for me:
<ComboBox
Name="ItemsControl"
IsEditable="True"
KeyUp="OnItemsControlKeyUp"
I check if the text has changed since the last time the filter was applied (to avoid filtering when a non-alphanumeric key is pressed).
private string _textBeforeFilter;
private void OnItemsControlKeyUp(object sender, KeyEventArgs e)
{
var arrowKey = e.Key >= Key.Left && e.Key <= Key.Down;
// if arrow key (navigating) or the text hasn't changed, then a we don't need to filter
if (arrowKey || ItemsControl.Text.EqualsIgnoreCase(_textBeforeFilter)) return;
_textBeforeFilter = ItemsControl.Text;
var textIsEmpty = string.IsNullOrWhiteSpace(ItemsControl.Text);
var itemsViewOriginal = (CollectionView) CollectionViewSource.GetDefaultView(ItemsControl.ItemsSource);
// if the text is empty, then we show everything, otherwise filter based on the text
itemsViewOriginal.Filter = o => textIsEmpty || ((string) o).ContainsIgnoreCase(ItemsControl.Text);
}
NOTE: EqualsIgnoreCase
and ContainsIgnoreCase
are extension methods:
public static bool EqualsIgnoreCase(this string source, string value)
{
return source.Equals(value, StringComparison.OrdinalIgnoreCase);
}
public static bool ContainsIgnoreCase(this string source, string value)
{
return source.Contains(value, StringComparison.OrdinalIgnoreCase);
}
Upvotes: 2
Reputation: 51
In XAML you should set IsEditable=True and add handler for PreviewKeyDown event:
private void ComboBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
var cmb = (ComboBox)sender;
cmb.IsDropDownOpen = true;
var textbox = cmb.Template.FindName("PART_EditableTextBox", cmb) as TextBox;
cmb.ItemsSource = CurrentStorage.Organisations.Where(p => string.IsNullOrEmpty(cmb.Text) || p.Name.ToLower().Contains(textbox.Text.ToLower())).ToList();
}
Upvotes: 1
Reputation: 622
After a lot of fiddling, I have managed to arrive at a complete, working solution. (Or so it seems.)
You need to modify your ComboBox like so:
<ComboBox
...
IsTextSearchEnabled="False"
...
PreviewTextInput="PreviewTextInput_EnhanceComboSearch"
PreviewKeyUp="PreviewKeyUp_EnhanceComboSearch"
DataObject.Pasting="Pasting_EnhanceComboSearch" />
ie. to disable default text search, and add events handlers that will take care of user adding, deleting and pasting text.
In order for PreviewTextInput_EnhanceComboSearch
and Pasting_EnhanceComboSearch
to work at all, you will need to access your ComboBox's caret. Unfortunately, to do this, you need to traverse, er, visual tree (hat tip to Matt Hamilton). You can do that in an extension method, but I used a static one in my Page
class:
public static T GetChildOfType<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj == null) return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null) return result;
}
return null;
}
Please note I used
s => s.IndexOf(e.Text, StringComparison.InvariantCultureIgnoreCase) != -1
which is equivalent to case-insensitive s => s.Contains(e.Text)
check. Remember to change that part to suit your needs.
When a PreviewTextInput
handler is run, the .Text
property inside the ComboBox contains the text from before it was modified. Therefore, we need to get ComboBox's internal TextBox using GetChildOfType
method in order to obtain its caret, so we know where exactly was the typed character inserted.
private void PreviewTextInput_EnhanceComboSearch(object sender, TextCompositionEventArgs e)
{
ComboBox cmb = (ComboBox)sender;
cmb.IsDropDownOpen = true;
if (!string.IsNullOrEmpty(cmb.Text))
{
string fullText = cmb.Text.Insert(GetChildOfType<TextBox>(cmb).CaretIndex, e.Text);
cmb.ItemsSource = Names.Where(s => s.IndexOf(fullText, StringComparison.InvariantCultureIgnoreCase) != -1).ToList();
}
else if (!string.IsNullOrEmpty(e.Text))
{
cmb.ItemsSource = Names.Where(s => s.IndexOf(e.Text, StringComparison.InvariantCultureIgnoreCase) != -1).ToList();
}
else
{
cmb.ItemsSource = Names;
}
}
DataObject.Pasting
handler behaves in a similar fashion to PreviewTextInput
hanlder, so we need the caret again.
private void Pasting_EnhanceComboSearch(object sender, DataObjectPastingEventArgs e)
{
ComboBox cmb = (ComboBox)sender;
cmb.IsDropDownOpen = true;
string pastedText = (string)e.DataObject.GetData(typeof(string));
string fullText = cmb.Text.Insert(GetChildOfType<TextBox>(cmb).CaretIndex, pastedText);
if (!string.IsNullOrEmpty(fullText))
{
cmb.ItemsSource = Names.Where(s => s.IndexOf(fullText, StringComparison.InvariantCultureIgnoreCase) != -1).ToList();
}
else
{
cmb.ItemsSource = Names;
}
}
This will trigger when the user depresses either Delete or Backspace.
And also Space, because Space is ignored by PreviewTextInput
, so it would be difficult to filter out "John" from "John Doe" and "John Richards" in the example.
private void PreviewKeyUp_EnhanceComboSearch(object sender, KeyEventArgs e)
{
if (e.Key == Key.Back || e.Key == Key.Delete)
{
ComboBox cmb = (ComboBox)sender;
cmb.IsDropDownOpen = true;
if (!string.IsNullOrEmpty(cmb.Text))
{
cmb.ItemsSource = Names.Where(s => s.IndexOf(cmb.Text, StringComparison.InvariantCultureIgnoreCase) != -1).ToList();
}
else
{
cmb.ItemsSource = Names;
}
}
}
...and that should probably be enough.
Upvotes: 11
Reputation: 9
I created an autocomplete to WPF
that could help you.
Follow the link below to github:
https://github.com/rhpontes/AutocompleteWpf
I hope it helps you.
Upvotes: 0
Reputation: 166
use PreviewTextInput event for do filter and show drop down like this:
private void ComboBox_TextInput_1(object sender, TextCompositionEventArgs e)
{
cmbperson.IsDropDownOpen = true;
cmbperson.ItemsSource = DataBase.Persons.Where(p => p.Name.Contains(e.Text)).ToList();
}
Upvotes: 6