I'm implementing a filterable Combobox but have been running into trouble. My implementation below works initially for typing to filter results but once you select an item from the dropdown it becomes difficult to backspace characters or delete all for the Textbox filter. It appears like the Combobox auto-selects the originally selected value and resets to that on each button press. Below is the minimum code to reproduce the issue which will demonstrate the issue clearer. Simply select a value from the dropdown and then backspace or delete text to try to select a new value.
With the following test data;
public IEnumerable<Fruit> Fruits { get; set; }
public MainWindow()
{
List<Fruit> temp = new List<Fruit>
{
new Fruit(Name = "apple"),
new Fruit(Name = "banana"),
new Fruit(Name = "grape"),
new Fruit(Name = "lemon"),
new Fruit(Name = "strawberry")
};
Fruits = temp;
DataContext = this;
InitializeComponent();
}
}
public class Fruit
{
public string Name { get; set; }
public Fruit(string name)
{
Name = name;
}
}
Updated FilteredComboBox code;
public class FilteredComboBox : ComboBox
{
public static readonly DependencyProperty MinimumSearchLengthProperty = DependencyProperty.Register(
"MinimumSearchLength",
typeof(int),
typeof(FilteredComboBox),
new UIPropertyMetadata(3));
public int MinimumSearchLength
{
get => (int)this.GetValue(FilteredComboBox.MinimumSearchLengthProperty);
set => SetValue(FilteredComboBox.MinimumSearchLengthProperty, value);
}
public TextBox PART_EditableTextBox { get; set; }
static FilteredComboBox()
{
// Register a delegate to handle property changes of the Text dependency property
ComboBox.TextProperty.OverrideMetadata(
typeof(FilteredComboBox),
new FrameworkPropertyMetadata(default, FilteredComboBox.FilterItemsOnTextChanged));
}
public FilteredComboBox()
{
this.IsTextSearchEnabled = false;
}
private static void FilterItemsOnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var this_ = d as FilteredComboBox;
this_.IsDropDownOpen = true;
this_.Items.Filter =
item => e.NewValue.ToString().Length < this_.MinimumSearchLength
|| ((Fruit)item).Name.Contains(e.NewValue.ToString());
}
/// <inheritdoc />
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.PART_EditableTextBox = GetTemplateChild("PART_EditableTextBox") as TextBox;
this.PART_EditableTextBox.TextChanged += RemoveTextSelectionOnTextBoxTextChanged;
}
/// <inheritdoc />
protected override void OnKeyUp(KeyEventArgs e)
{
base.OnKeyUp(e);
switch (e.Key)
{
// Clear the filter and reset the text field
case Key.Tab:
case Key.Enter:
this.Items.Filter = null;
this.PART_EditableTextBox.Text = string.Empty;
break;
case Key.Down:
this.IsDropDownOpen = true;
break;
}
}
private void RemoveTextSelectionOnTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
if (this.PART_EditableTextBox.SelectionLength > 0)
{
this.PART_EditableTextBox.CaretIndex = e.Changes.FirstOrDefault()?.Offset + 1
?? this.PART_EditableTextBox.Text.Length;
}
}
}
And am implementing it in a test solution like so;
<local:FilteredComboBox
MinimumSearchLength="2"
IsEditable="True"
ItemsSource="{Binding Fruits}"
DisplayMemberPath = "Name">
</local:FilteredComboBox>