Generic.xaml:
Right Click `CustomControl1.cs` and selected `Rename` , then edit the class named `MyComboBox` .
MyComboBox.cs:
using System;
using System.Collections.Generic;
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 WpfCustomControlLibrary2
{
public static class Extensions
{
public static bool TryFindVisualChildElement(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;
}
}
public class MyComboBox : Control
{
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 TextBoxComboBoxMap { get; }
private static Dictionary TextBoxSelectionStartMap { get; }
private static Dictionary ComboBoxTextBoxMap { get; }
private static bool IsNavigationKeyPressed { get; set; }
static MyComboBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyComboBox), new FrameworkPropertyMetadata(typeof(MyComboBox)));
MyComboBox.TextBoxComboBoxMap = new Dictionary();
MyComboBox.TextBoxSelectionStartMap = new Dictionary();
MyComboBox.ComboBoxTextBoxMap = new Dictionary();
}
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;
}
}
}
}