Szabolcs Antal
Szabolcs Antal

Reputation: 957

WPF Custom ComboBox with filtering becomes unresponsive when deleting characters

Scenario:

I'm implementing a custom ComboBox in WPF with a filtering feature:

Find the full implementation on the bottom.

Issue:

When the total number of items (before filtering) is below 140, everything works correctly:

However, when the total number of items exceeds 150, strange behavior occurs when deleting characters from the TextBox:

Questions:

My requirement is that the TextBox to behave as expected in all cases. I find this cursor behavior very unexpected and unusual.

<Window x:Class="FilterableComboBox.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:FilterableComboBox"
        Title="MainWindow"
        Height="100"
        Width="400">
    <Window.Resources>
        <local:StringContainsConverter x:Key="StringContainsConverter" />
    </Window.Resources>

    <Grid>
        <ComboBox x:Name="FilterableComboBox"
                  HorizontalAlignment="Stretch"
                  Height="25"
                  Width="300"
                  ItemsSource="{Binding CustomValues}">

            <ComboBox.ItemContainerStyle>
                <Style TargetType="ComboBoxItem">
                    <Setter Property="Visibility">
                        <Setter.Value>
                            <MultiBinding Converter="{StaticResource StringContainsConverter}">
                                <Binding  />
                                <Binding ElementName="FilterTextBox" Path="Text" />
                            </MultiBinding>
                        </Setter.Value>
                    </Setter>
                </Style>
            </ComboBox.ItemContainerStyle>
            
            <ComboBox.Template>
                <ControlTemplate TargetType="ComboBox">
                    <Grid>
                        <ToggleButton Content="{TemplateBinding SelectedItem}"
                                      IsChecked="{Binding Path=IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}}" />
                        <Popup x:Name="Popup"
                               Width="{Binding ElementName=FilterableComboBox, Path=ActualWidth}"
                               IsOpen="{TemplateBinding IsDropDownOpen}"
                               Placement="Bottom"
                               MaxHeight="200">
                            <Grid>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="auto" />
                                    <RowDefinition Height="*" />
                                </Grid.RowDefinitions>
                                <TextBox Grid.Row="0"
                                         x:Name="FilterTextBox"
                                         HorizontalAlignment="Stretch" />
                                <ScrollViewer Grid.Row="1"
                                              Background="White">
                                    <ItemsPresenter />
                                </ScrollViewer>
                            </Grid>
                        </Popup>
                    </Grid>
                </ControlTemplate>
            </ComboBox.Template>
        </ComboBox>
    </Grid>
</Window>
using System.ComponentModel;
using System.Windows;

namespace FilterableComboBox
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new MainViewModel();
        }
    }

    public class MainViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler? PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        public MainViewModel()
        {
            _customValues = new List<string>();
            for (int i = 0; i < 500; i++)
                _customValues.Add(i.ToString());
        }


        private string _filterText = string.Empty;
        public string FilterText
        {
            get
            {
                return _filterText;
            }
            set
            {
                _filterText = value;
                OnPropertyChanged(nameof(FilterText));
            }
        }

        private IList<string> _customValues;
        public IList<string> CustomValues
        {
            get
            {
                return _customValues;
            }
            set
            {
                _customValues = value;
                OnPropertyChanged(nameof(CustomValues));
            }
        }
    }
}
using System.Globalization;
using System.Windows;
using System.Windows.Data;

namespace FilterableComboBox
{
    public class StringContainsConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter,CultureInfo culture)
        {
            string text = (string)values[0];
            string filterText = (string)values[1];

            return text.Contains(filterText)
                ? Visibility.Visible
                : Visibility.Collapsed;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

Upvotes: 0

Views: 95

Answers (0)

Related Questions