WPF 自定义可分列下拉框:模糊搜索、高性能数据展示
实现思路:
-
继承自ComboBox类,重写其模板,实现下拉框的样式和行为。
-
在模板中添加一个TextBox控件,用于输入筛选条件。
-
在模板中添加一个ItemsControl控件,用于显示下拉列表。
-
在自定义下拉框类中添加一个ItemsSource属性,用于绑定数据源。
-
在自定义下拉框类中添加一个FilterText属性,用于绑定筛选条件。
-
在自定义下拉框类中添加一个Filter方法,用于根据筛选条件过滤数据源,并更新下拉列表。
-
在自定义下拉框类中添加一个Columns属性,用于设置下拉列表的列数和列宽。
-
在模板中使用Grid控件实现下拉列表的分列显示。
-
在模板中使用Trigger控件实现下拉列表的弹出和收起。
-
在自定义下拉框类中重写OnApplyTemplate方法,获取模板中的控件引用,并绑定事件。
-
在自定义下拉框类中重写MeasureOverride和ArrangeOverride方法,实现下拉列表的自适应宽度和高度。
下面是完整的代码实现:
public class MultiColumnComboBox : ComboBox
{
private TextBox _filterTextBox;
private ItemsControl _itemsControl;
private bool _isFiltering = false;
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register("Columns", typeof(int), typeof(MultiColumnComboBox), new PropertyMetadata(1));
public int Columns
{
get { return (int)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
public static readonly DependencyProperty ColumnWidthProperty =
DependencyProperty.Register("ColumnWidth", typeof(double), typeof(MultiColumnComboBox), new PropertyMetadata(100.0));
public double ColumnWidth
{
get { return (double)GetValue(ColumnWidthProperty); }
set { SetValue(ColumnWidthProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(MultiColumnComboBox), new PropertyMetadata(null, OnItemsSourceChanged));
public new IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty FilterTextProperty =
DependencyProperty.Register("FilterText", typeof(string), typeof(MultiColumnComboBox), new PropertyMetadata("", OnFilterTextChanged));
public string FilterText
{
get { return (string)GetValue(FilterTextProperty); }
set { SetValue(FilterTextProperty, value); }
}
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var comboBox = (MultiColumnComboBox)d;
comboBox.Filter();
}
private static void OnFilterTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var comboBox = (MultiColumnComboBox)d;
if (!comboBox._isFiltering)
{
comboBox.Filter();
}
}
public MultiColumnComboBox()
{
DefaultStyleKey = typeof(MultiColumnComboBox);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_filterTextBox = GetTemplateChild("PART_FilterTextBox") as TextBox;
_itemsControl = GetTemplateChild("PART_ItemsControl") as ItemsControl;
if (_filterTextBox != null)
{
_filterTextBox.TextChanged += OnFilterTextBoxTextChanged;
}
}
protected override Size MeasureOverride(Size constraint)
{
var size = base.MeasureOverride(constraint);
if (_itemsControl != null)
{
var width = Columns * ColumnWidth;
var height = Math.Min(Items.Count, 10) * _itemsControl.ItemContainerGenerator.ContainerFromIndex(0).ActualHeight;
_itemsControl.Measure(new Size(width, height));
size.Width = Math.Max(size.Width, width);
size.Height = Math.Max(size.Height, _itemsControl.DesiredSize.Height + _filterTextBox.DesiredSize.Height);
}
return size;
}
protected override Size ArrangeOverride(Size arrangeBounds)
{
var size = base.ArrangeOverride(arrangeBounds);
if (_itemsControl != null && IsDropDownOpen)
{
var width = Columns * ColumnWidth;
var height = Math.Min(Items.Count, 10) * _itemsControl.ItemContainerGenerator.ContainerFromIndex(0).ActualHeight;
var x = 0.0;
var y = _filterTextBox.ActualHeight;
_itemsControl.Arrange(new Rect(x, y, width, height));
}
return size;
}
private void OnFilterTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
FilterText = _filterTextBox.Text;
}
private void Filter()
{
if (ItemsSource == null || _itemsControl == null)
{
return;
}
_isFiltering = true;
var view = CollectionViewSource.GetDefaultView(ItemsSource);
view.Filter = item =>
{
if (string.IsNullOrEmpty(FilterText))
{
return true;
}
var properties = item.GetType().GetProperties();
foreach (var property in properties)
{
var value = property.GetValue(item)?.ToString();
if (value != null && value.IndexOf(FilterText, StringComparison.OrdinalIgnoreCase) >= 0)
{
return true;
}
}
return false;
};
Items.Clear();
foreach (var item in view)
{
Items.Add(item);
}
_isFiltering = false;
}
}
在模板中,我们使用了一个Grid控件来实现下拉列表的分列显示,其中每个单元格都是一个ItemsControl控件,用于显示数据。我们还使用了一个Trigger控件来实现下拉列表的弹出和收起,当IsDropDownOpen属性为true时,显示下拉列表,否则隐藏下拉列表。下面是完整的模板代码:
<Style TargetType="{x:Type local:MultiColumnComboBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MultiColumnComboBox}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBox x:Name="PART_FilterTextBox" Grid.Row="0" Margin="2" VerticalAlignment="Center"/>
<Grid x:Name="PART_ItemsGrid" Grid.Row="1" Visibility="Collapsed">
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Style.Triggers>
<Trigger Property="IsVisible" Value="True">
<Setter Property="Visibility" Value="Visible"/>
<Setter Property="Grid.RowSpan" Value="2"/>
</Trigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ItemsControl x:Name="PART_ItemsControl1" Grid.Column="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="2" BorderThickness="0" ItemsSource="{TemplateBinding ItemsSource}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Property1}" Margin="2"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl x:Name="PART_ItemsControl2" Grid.Column="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="2" BorderThickness="0" ItemsSource="{TemplateBinding ItemsSource}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Property2}" Margin="2"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl x:Name="PART_ItemsControl3" Grid.Column="2" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="2" BorderThickness="0" ItemsSource="{TemplateBinding ItemsSource}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Property3}" Margin="2"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl x:Name="PART_ItemsControl4" Grid.Column="3" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="2" BorderThickness="0" ItemsSource="{TemplateBinding ItemsSource}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Property4}" Margin="2"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsDropDownOpen" Value="True">
<Setter TargetName="PART_ItemsGrid" Property="Visibility" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
原文地址: https://www.cveoy.top/t/topic/kqHM 著作权归作者所有。请勿转载和采集!