I'm trying to implement a custom combobox control that I found here :
https://stackoverflow.com/a/48559118
(I cannot post a comment because of my low reputation)
This custom combobox is designed to filter items in a combobox based on text input.
The solution mostly works except for one thing. When a user types a text in the combobox, then select an entry it is not possible anymore to do a SelectedItem = xyz from codebehind (same issue with binding, wanted to make a simple sample to reproduce the issue).
Sample Code :
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var testLst = new List<string>(new string[] { "element1", "element2", "element3" });
cmbTest.ItemsSource = testLst;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
cmbTest.SelectedItem = "element1";
}
}
Xaml :
<Window x:Class="TestList.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:TestList"
xmlns:mycontrol="clr-namespace:MyControls"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<StackPanel Width="150" Height="150" Orientation="Vertical">
<mycontrol:FilteredComboBox x:Name="cmbTest" IsEditable="True" IsTextSearchEnabled="False" StaysOpenOnEdit="True" >
</mycontrol:FilteredComboBox>
<Button Content="SelectItem" Click="Button_Click"></Button>
</StackPanel>
</Grid>
</Window>
I think that the issue might be related to the fact that the list doesn't contain anymore the item because it has been filtered out ?!
I struggle to find a way to fix this in the custom control.
Edit, custom control code :
using System.Collections;
using System.Diagnostics;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
namespace MyControls
{
public class FilteredComboBox : ComboBox
{
private string oldFilter = string.Empty;
private string currentFilter = string.Empty;
protected TextBox EditableTextBox => GetTemplateChild("PART_EditableTextBox") as TextBox;
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
if (newValue != null)
{
var view = CollectionViewSource.GetDefaultView(newValue);
view.Filter += FilterItem;
}
if (oldValue != null)
{
var view = CollectionViewSource.GetDefaultView(oldValue);
if (view != null) view.Filter -= FilterItem;
}
base.OnItemsSourceChanged(oldValue, newValue);
}
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
switch (e.Key)
{
case Key.Tab:
case Key.Enter:
IsDropDownOpen = false;
break;
case Key.Escape:
IsDropDownOpen = false;
SelectedIndex = -1;
Text = currentFilter;
break;
default:
if (e.Key == Key.Down) IsDropDownOpen = true;
base.OnPreviewKeyDown(e);
break;
}
// Cache text
oldFilter = Text;
}
protected override void OnKeyUp(KeyEventArgs e)
{
switch (e.Key)
{
case Key.Up:
case Key.Down:
break;
case Key.Tab:
case Key.Enter:
ClearFilter();
break;
default:
if (Text != oldFilter)
{
var temp = Text;
RefreshFilter(); //RefreshFilter will change Text property
Text = temp;
if (SelectedIndex != -1 && Text != Items[SelectedIndex].ToString())
{
SelectedIndex = -1; //Clear selection. This line will also clear Text property
Text = temp;
}
IsDropDownOpen = true;
EditableTextBox.SelectionStart = int.MaxValue;
}
//automatically select the item when the input text matches it
for (int i = 0; i < Items.Count; i++)
{
if (Text == Items[i].ToString())
SelectedIndex = i;
}
base.OnKeyUp(e);
currentFilter = Text;
break;
}
}
protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
ClearFilter();
var temp = SelectedIndex;
SelectedIndex = -1;
Text = string.Empty;
SelectedIndex = temp;
base.OnPreviewLostKeyboardFocus(e);
}
private void RefreshFilter()
{
if (ItemsSource == null) return;
var view = CollectionViewSource.GetDefaultView(ItemsSource);
view.Refresh();
}
private void ClearFilter()
{
currentFilter = string.Empty;
RefreshFilter();
}
private bool FilterItem(object value)
{
if (value == null) return false;
if (Text.Length == 0) return true;
return value.ToString().ToLower().Contains(Text.ToLower());
}
}
}