Reputation: 957
Scenario:
I'm implementing a custom ComboBox
in WPF with a filtering feature:
ComboBox
contains a Popup
with a TextBox
for filtering.TextBox
, a list displays only the items that match the filter text.Visibility.Collapsed
, which is applied via ItemContainerStyle
and a converter.Find the full implementation on the bottom.
Issue:
When the total number of items (before filtering) is below 140, everything works correctly:
TextBox
filters the list dynamically.However, when the total number of items exceeds 150, strange behavior occurs when deleting characters from the TextBox
:
"1a"
, filtering works properly."a"
, the TextBox
becomes unresponsive:
Questions:
My requirement is that the TextBox
to behave as expected in all cases. I find this cursor behavior very unexpected and unusual.
ComboBox
— so I cannot use VirtualizingStackPanel
due to limitations in my actual use case.<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