WPF 实现筛选下拉多选控件
- 框架使用
.NET4 至 .NET6
; Visual Studio 2022
;
创建 MultiSelectionSearchComboBox
继承 Control
.
模板中创建两个 ListBox
PART_Selector
用来显示所有Item
PART_SearchSelector
用于记录筛选到的Item
Item
继承 ListBoxItem
并将样式模板修改为 CheckBox
解决 Popup
中 IME
不跟随 TextBox
问题
private static extern IntPtr SetFocus(IntPtr hWnd);
当 TextBox
中 Text
发生改变时通过循环 Item
中的 GetProperty
获取特定 DisplayMemberPath
属性,判断是否包含输入的 SearchText
值,如果包含则添加PART_SearchSelector
中,当 SelectionChanged
事件更改时将修改 PART_Selector
中的内容。
实现代码
1)创建 MultiSelectionSearchComboBox.cs
代码如下:
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Runtime.InteropServices; | |
using System.Windows; | |
using System.Windows.Controls; | |
using System.Windows.Controls.Primitives; | |
using System.Windows.Interop; | |
namespace WPFDevelopers.Controls | |
{ | |
[ | ]|
[ | ]|
[ | ]|
[ | ]|
[ | ]|
public class MultiSelectionSearchComboBox : Control | |
{ | |
private const string TextBoxTemplateName = "PART_TextBox"; | |
private const string PopupTemplateName = "PART_Popup"; | |
private const string ListBoxTemplateName = "PART_Selector"; | |
private const string CheckBoxTemplateName = "PART_SelectAll"; | |
private const string ListBoxTemplateNameSearch = "PART_SearchSelector"; | |
public static readonly RoutedEvent ClosedEvent = | |
EventManager.RegisterRoutedEvent("Closed", | |
RoutingStrategy.Bubble, | |
typeof(RoutedEventHandler), | |
typeof(MultiSelectionSearchComboBox)); | |
public static readonly DependencyProperty DisplayMemberPathProperty = | |
DependencyProperty.Register("DisplayMemberPath", | |
typeof(string), | |
typeof(MultiSelectionSearchComboBox), | |
new PropertyMetadata(string.Empty)); | |
public static readonly DependencyProperty SelectedValuePathProperty = | |
DependencyProperty.Register("SelectedValuePath", | |
typeof(string), | |
typeof(MultiSelectionSearchComboBox), | |
new PropertyMetadata(string.Empty)); | |
public static readonly DependencyProperty TextProperty = | |
DependencyProperty.Register("Text", | |
typeof(string), | |
typeof(MultiSelectionSearchComboBox), | |
new PropertyMetadata(string.Empty)); | |
public static readonly DependencyProperty ItemsSourceProperty = | |
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(MultiSelectionSearchComboBox), | |
new PropertyMetadata()); | |
public static readonly DependencyProperty ItemsSourceSearchProperty = | |
DependencyProperty.Register("ItemsSourceSearch", typeof(IEnumerable), typeof(MultiSelectionSearchComboBox), | |
new PropertyMetadata()); | |
public static readonly DependencyProperty SelectAllContentProperty = | |
DependencyProperty.Register("SelectAllContent", typeof(object), typeof(MultiSelectionSearchComboBox), | |
new PropertyMetadata("全选")); | |
public static readonly DependencyProperty IsSelectAllActiveProperty = | |
DependencyProperty.Register("IsSelectAllActive", typeof(bool), typeof(MultiSelectionSearchComboBox), | |
new PropertyMetadata(false)); | |
public static readonly DependencyProperty DelimiterProperty = | |
DependencyProperty.Register("Delimiter", typeof(string), typeof(MultiSelectionSearchComboBox), | |
new PropertyMetadata(";")); | |
public static readonly DependencyProperty IsDropDownOpenProperty = | |
DependencyProperty.Register("IsDropDownOpen", typeof(bool), typeof(MultiSelectionSearchComboBox), | |
new PropertyMetadata(false, OnIsDropDownOpenChanged)); | |
public static readonly DependencyProperty MaxDropDownHeightProperty = | |
DependencyProperty.Register("MaxDropDownHeight", typeof(double), typeof(MultiSelectionSearchComboBox), | |
new UIPropertyMetadata(SystemParameters.PrimaryScreenHeight / 3.0, OnMaxDropDownHeightChanged)); | |
public static readonly DependencyProperty SelectedItemsProperty = | |
DependencyProperty.Register("SelectedItems", typeof(IList), typeof(MultiSelectionSearchComboBox), | |
new FrameworkPropertyMetadata(null, | |
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal, | |
OnSelectedItemsChanged)); | |
public static readonly DependencyProperty SearchWatermarkProperty = | |
DependencyProperty.Register("SearchWatermark", | |
typeof(string), | |
typeof(MultiSelectionSearchComboBox), | |
new PropertyMetadata(string.Empty)); | |
private CheckBox _checkBox; | |
private ListBox _listBox; | |
private ListBox _listBoxSearch; | |
private Popup _popup; | |
private TextBox _textBox; | |
private List<object> selectedItems; | |
private List<object> selectedList; | |
private List<object> selectedSearchList; | |
private string theLastText; | |
static MultiSelectionSearchComboBox() | |
{ | |
DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiSelectionSearchComboBox), | |
new FrameworkPropertyMetadata(typeof(MultiSelectionSearchComboBox))); | |
} | |
public string Delimiter | |
{ | |
get => (string)GetValue(DelimiterProperty); | |
set => SetValue(DelimiterProperty, value); | |
} | |
public string SelectedValuePath | |
{ | |
get => (string)GetValue(SelectedValuePathProperty); | |
set => SetValue(SelectedValuePathProperty, value); | |
} | |
public string DisplayMemberPath | |
{ | |
get => (string)GetValue(DisplayMemberPathProperty); | |
set => SetValue(DisplayMemberPathProperty, value); | |
} | |
public string Text | |
{ | |
get => (string) GetValue(TextProperty); | |
set => SetValue(TextProperty, value); | |
} | |
public IEnumerable ItemsSource | |
{ | |
get => (IEnumerable)GetValue(ItemsSourceProperty); | |
set => SetValue(ItemsSourceProperty, value); | |
} | |
public IEnumerable ItemsSourceSearch | |
{ | |
get => (IEnumerable) GetValue(ItemsSourceSearchProperty); | |
set => SetValue(ItemsSourceSearchProperty, value); | |
} | |
public object SelectAllContent | |
{ | |
get => GetValue(SelectAllContentProperty); | |
set => SetValue(SelectAllContentProperty, value); | |
} | |
public bool IsSelectAllActive | |
{ | |
get => (bool) GetValue(IsSelectAllActiveProperty); | |
set => SetValue(IsSelectAllActiveProperty, value); | |
} | |
public bool IsDropDownOpen | |
{ | |
get => (bool) GetValue(IsDropDownOpenProperty); | |
set => SetValue(IsDropDownOpenProperty, value); | |
} | |
public double MaxDropDownHeight | |
{ | |
get => (double) GetValue(MaxDropDownHeightProperty); | |
set => SetValue(MaxDropDownHeightProperty, value); | |
} | |
public IList SelectedItems | |
{ | |
get => (IList) GetValue(SelectedItemsProperty); | |
set => SetValue(SelectedItemsProperty, value); | |
} | |
public string SearchWatermark | |
{ | |
get => (string)GetValue(SearchWatermarkProperty); | |
set => SetValue(SearchWatermarkProperty, value); | |
} | |
[ | ]|
private static extern IntPtr SetFocus(IntPtr hWnd); | |
public event RoutedEventHandler Closed | |
{ | |
add => AddHandler(ClosedEvent, value); | |
remove => RemoveHandler(ClosedEvent, value); | |
} | |
public override void OnApplyTemplate() | |
{ | |
base.OnApplyTemplate(); | |
selectedList = new List<object>(); | |
selectedSearchList = new List<object>(); | |
selectedItems = new List<object>(); | |
_textBox = GetTemplateChild(TextBoxTemplateName) as TextBox; | |
_popup = GetTemplateChild(PopupTemplateName) as Popup; | |
if (_popup != null) | |
_popup.GotFocus += _popup_GotFocus; | |
_listBox = GetTemplateChild(ListBoxTemplateName) as ListBox; | |
_checkBox = GetTemplateChild(CheckBoxTemplateName) as CheckBox; | |
_listBoxSearch = GetTemplateChild(ListBoxTemplateNameSearch) as ListBox; | |
if (_textBox != null) | |
{ | |
_textBox.TextChanged -= _textbox_TextChanged; | |
_textBox.TextChanged += _textbox_TextChanged; | |
} | |
if (_checkBox != null) | |
{ | |
_checkBox.Checked -= _checkBox_Checked; | |
_checkBox.Unchecked -= _checkBox_Unchecked; | |
_checkBox.Checked += _checkBox_Checked; | |
_checkBox.Unchecked += _checkBox_Unchecked; | |
} | |
if (_listBox != null) | |
{ | |
_listBox.IsVisibleChanged -= _listBox_IsVisibleChanged; | |
_listBox.IsVisibleChanged += _listBox_IsVisibleChanged; | |
_listBox.SelectionChanged -= _listBox_SelectionChanged; | |
_listBox.SelectionChanged += _listBox_SelectionChanged; | |
} | |
if (_listBoxSearch != null) | |
{ | |
_listBoxSearch.IsVisibleChanged -= _listBoxSearch_IsVisibleChanged; | |
_listBoxSearch.IsVisibleChanged += _listBoxSearch_IsVisibleChanged; | |
_listBoxSearch.SelectionChanged -= _listBoxSearch_SelectionChanged; | |
_listBoxSearch.SelectionChanged += _listBoxSearch_SelectionChanged; | |
} | |
} | |
private void _listBoxSearch_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) | |
{ | |
if ((bool) e.NewValue) | |
UpdateIsChecked(_listBoxSearch); | |
} | |
private void _listBox_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) | |
{ | |
if ((bool) e.NewValue) | |
{ | |
foreach (var item in selectedSearchList) | |
if (!_listBox.SelectedItems.Contains(item)) | |
_listBox.SelectedItems.Add(item); | |
UpdateIsChecked(_listBox); | |
} | |
} | |
private void UpdateIsChecked(ListBox listBox) | |
{ | |
_checkBox.Checked -= _checkBox_Checked; | |
if (listBox.Items.Count > 0 && listBox.Items.Count == listBox.SelectedItems.Count) | |
{ | |
if (_checkBox.IsChecked != true) | |
_checkBox.IsChecked = true; | |
} | |
else | |
{ | |
if (listBox.SelectedItems.Count == 0) | |
_checkBox.IsChecked = false; | |
else | |
_checkBox.IsChecked = null; | |
} | |
_checkBox.Checked += _checkBox_Checked; | |
} | |
private void _popup_GotFocus(object sender, RoutedEventArgs e) | |
{ | |
var source = (HwndSource) PresentationSource.FromVisual(_popup.Child); | |
if (source != null) | |
{ | |
SetFocus(source.Handle); | |
_textBox.Focus(); | |
} | |
} | |
private void _checkBox_Unchecked(object sender, RoutedEventArgs e) | |
{ | |
if (_listBoxSearch.Visibility == Visibility.Visible) | |
_listBoxSearch.UnselectAll(); | |
else | |
_listBox.UnselectAll(); | |
} | |
private void _checkBox_Checked(object sender, RoutedEventArgs e) | |
{ | |
if (_listBoxSearch.Visibility == Visibility.Visible) | |
_listBoxSearch.SelectAll(); | |
else | |
_listBox.SelectAll(); | |
} | |
private void Combination() | |
{ | |
var seletedName = new List<string>(); | |
foreach (var item in _listBox.SelectedItems) | |
{ | |
var name = GetDisplayText(item); | |
seletedName.Add(name); | |
} | |
foreach (var item in _listBoxSearch.SelectedItems) | |
{ | |
if (_listBox.SelectedItems.Contains(item)) | |
continue; | |
var name = GetDisplayText(item); | |
seletedName.Add(name); | |
} | |
Text = string.Join(Delimiter, seletedName.ToArray()); | |
} | |
private void _listBox_SelectionChanged(object sender, SelectionChangedEventArgs e) | |
{ | |
if (e.RemovedItems.Count > 0) | |
foreach (var item in e.RemovedItems) | |
if (_checkBox.IsChecked == true) | |
{ | |
_checkBox.Unchecked -= _checkBox_Unchecked; | |
if (_listBox.Items.Count == 1) | |
_checkBox.IsChecked = false; | |
else | |
_checkBox.IsChecked = null; | |
_checkBox.Unchecked += _checkBox_Unchecked; | |
} | |
if (e.AddedItems.Count > 0) | |
SelectionChecked(_listBox); | |
Combination(); | |
SelectedItems = _listBox.SelectedItems; | |
} | |
private void _listBoxSearch_SelectionChanged(object sender, SelectionChangedEventArgs e) | |
{ | |
if (!_listBoxSearch.IsVisible) return; | |
if (e.RemovedItems.Count > 0) | |
{ | |
foreach (var item in e.RemovedItems) | |
if (selectedSearchList.Contains(item)) | |
selectedSearchList.Remove(item); | |
Combination(); | |
SelectionChecked(_listBoxSearch); | |
} | |
if (e.AddedItems.Count > 0) | |
{ | |
foreach (var item in e.AddedItems) | |
if (!selectedSearchList.Contains(item)) | |
selectedSearchList.Add(item); | |
Combination(); | |
SelectionChecked(_listBoxSearch); | |
} | |
} | |
private void SelectionChecked(ListBox listbox) | |
{ | |
if (listbox.SelectedItems.Count > 0 | |
&& | |
listbox.Items.Count == listbox.SelectedItems.Count) | |
{ | |
_checkBox.Checked -= _checkBox_Checked; | |
_checkBox.IsChecked = true; | |
_checkBox.Checked += _checkBox_Checked; | |
} | |
else | |
{ | |
_checkBox.Checked -= _checkBox_Checked; | |
if (listbox.SelectedItems.Count > 0 | |
&& | |
listbox.Items.Count == listbox.SelectedItems.Count) | |
{ | |
if (_checkBox.IsChecked != true) | |
_checkBox.IsChecked = true; | |
} | |
else | |
{ | |
if (listbox.SelectedItems.Count == 0) | |
_checkBox.IsChecked = false; | |
else | |
_checkBox.IsChecked = null; | |
} | |
_checkBox.Checked += _checkBox_Checked; | |
} | |
} | |
private string GetDisplayText(object dataItem, string path = null) | |
{ | |
if (dataItem == null) return string.Empty; | |
return GetPropertyValue(dataItem); | |
} | |
private void _textbox_TextChanged(object sender, TextChangedEventArgs e) | |
{ | |
if (string.IsNullOrWhiteSpace(theLastText)) theLastText = _textBox.Text; | |
SearchText(_textBox.Text); | |
} | |
private void SearchText(string _text) | |
{ | |
var text = _text; | |
if (string.IsNullOrWhiteSpace(text)) | |
{ | |
if (_listBoxSearch.Visibility != Visibility.Collapsed) | |
_listBoxSearch.Visibility = Visibility.Collapsed; | |
if (_listBox.Visibility != Visibility.Visible) | |
_listBox.Visibility = Visibility.Visible; | |
} | |
else | |
{ | |
if(_listBoxSearch.Visibility != Visibility.Visible) | |
_listBoxSearch.Visibility = Visibility.Visible; | |
if(_listBox.Visibility != Visibility.Collapsed) | |
_listBox.Visibility = Visibility.Collapsed; | |
var listSearch = new List<object>(); | |
foreach (var item in _listBox.Items) | |
{ | |
var str = GetPropertyValue(item); | |
if (!string.IsNullOrWhiteSpace(str)) | |
if (str.Contains(text.ToUpperInvariant())) | |
listSearch.Add(item); | |
} | |
foreach (var item in selectedList) | |
if (!listSearch.Contains(item)) | |
listSearch.Add(item); | |
var lastItem = ItemsSourceSearch; | |
ItemsSourceSearch = listSearch; | |
SelectionChecked(_listBoxSearch); | |
selectedItems.Clear(); | |
foreach (var item in _listBoxSearch.Items) | |
if (_listBox.SelectedItems.Contains(item)) | |
if (!_listBoxSearch.SelectedItems.Contains(item)) | |
_listBoxSearch.SelectedItems.Add(item); | |
} | |
} | |
private string GetPropertyValue(object item) | |
{ | |
var result = string.Empty; | |
var nameParts = DisplayMemberPath.Split('.'); | |
if (nameParts.Length == 1) | |
{ | |
var property = item.GetType().GetProperty(DisplayMemberPath); | |
if (property != null) | |
return (property.GetValue(item, null) ?? string.Empty).ToString(); | |
} | |
return result.ToUpperInvariant(); | |
} | |
private static void OnIsDropDownOpenChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) | |
{ | |
var multiSelectionSearchComboBox = o as MultiSelectionSearchComboBox; | |
if (multiSelectionSearchComboBox != null) | |
multiSelectionSearchComboBox.OnIsOpenChanged((bool) e.OldValue, (bool) e.NewValue); | |
} | |
protected virtual void OnIsOpenChanged(bool oldValue, bool newValue) | |
{ | |
if (!newValue) | |
RaiseRoutedEvent(ClosedEvent); | |
} | |
private void RaiseRoutedEvent(RoutedEvent routedEvent) | |
{ | |
var args = new RoutedEventArgs(routedEvent, this); | |
RaiseEvent(args); | |
} | |
private static void OnMaxDropDownHeightChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) | |
{ | |
var comboBox = o as MultiSelectionSearchComboBox; | |
if (comboBox != null) | |
comboBox.OnMaxDropDownHeightChanged((double) e.OldValue, (double) e.NewValue); | |
} | |
protected virtual void OnMaxDropDownHeightChanged(double oldValue, double newValue) | |
{ | |
} | |
private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) | |
{ | |
var mltiSelectionSearchComboBox = d as MultiSelectionSearchComboBox; | |
if (e.NewValue != null) | |
{ | |
var collection = e.NewValue as IList; | |
if (collection.Count <= 0) return; | |
if (e.OldValue != null && mltiSelectionSearchComboBox._listBox != null) | |
{ | |
mltiSelectionSearchComboBox._listBox.SelectionChanged -= | |
mltiSelectionSearchComboBox._listBox_SelectionChanged; | |
mltiSelectionSearchComboBox._listBox.SelectedItems.Clear(); | |
} | |
foreach (var item in collection) | |
{ | |
var name = mltiSelectionSearchComboBox.GetPropertyValue(item); | |
var model = mltiSelectionSearchComboBox._listBox.ItemsSource.OfType<object>().FirstOrDefault(h => | |
mltiSelectionSearchComboBox.GetPropertyValue(h).ToUpperInvariant() | |
.Equals(name)); | |
if (model != null && !mltiSelectionSearchComboBox._listBox.SelectedItems.Contains(item)) | |
mltiSelectionSearchComboBox._listBox.SelectedItems.Add(model); | |
if (e.OldValue != null && mltiSelectionSearchComboBox._listBox != null) | |
mltiSelectionSearchComboBox._listBox.SelectionChanged += | |
mltiSelectionSearchComboBox._listBox_SelectionChanged; | |
mltiSelectionSearchComboBox.Combination(); | |
} | |
} | |
} | |
} | |
} |
2)创建 MultiSelectionListBox.xaml
代码如下:
using System.Windows; | |
using System.Windows.Controls; | |
namespace WPFDevelopers.Controls | |
{ | |
public class MultiSelectionListBox:ListBox | |
{ | |
protected override bool IsItemItsOwnContainerOverride(object item) | |
{ | |
return item is MultiSelectComboBoxItem; | |
} | |
protected override DependencyObject GetContainerForItemOverride() | |
{ | |
return new MultiSelectComboBoxItem(); | |
} | |
} | |
} |
3)创建 MultiSelectionSearchComboBox.xaml
代码如下:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | |
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | |
xmlns:controls="clr-namespace:WPFDevelopers.Controls" | |
xmlns:helpers="clr-namespace:WPFDevelopers.Helpers"> | |
<ResourceDictionary.MergedDictionaries> | |
<ResourceDictionary Source="Basic/ControlBasic.xaml"/> | |
<ResourceDictionary Source="MultiSelectionComboBox.xaml"/> | |
</ResourceDictionary.MergedDictionaries> | |
<Style TargetType="{x:Type controls:MultiSelectionSearchComboBox}"> | |
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" /> | |
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" /> | |
<Setter Property="ScrollViewer.CanContentScroll" Value="True" /> | |
<Setter Property="MinWidth" Value="120" /> | |
<Setter Property="MinHeight" Value="{StaticResource MinHeight}" /> | |
<Setter Property="Height" Value="{StaticResource MinHeight}" /> | |
<Setter Property="HorizontalContentAlignment" Value="Left" /> | |
<Setter Property="VerticalContentAlignment" Value="Center" /> | |
<Setter Property="BorderBrush" Value="{DynamicResource BaseSolidColorBrush}"/> | |
<Setter Property="BorderThickness" Value="1"/> | |
<Setter Property="Background" Value="{DynamicResource BackgroundSolidColorBrush}"/> | |
<Setter Property="Padding" Value="14.5,3,30,3"/> | |
<Setter Property="Template"> | |
<Setter.Value> | |
<ControlTemplate TargetType="{x:Type controls:MultiSelectionSearchComboBox}"> | |
<ControlTemplate.Resources> | |
<Storyboard x:Key="OpenStoryboard"> | |
<DoubleAnimation Storyboard.TargetName="PART_DropDown" | |
Storyboard.TargetProperty="(Grid.RenderTransform).(ScaleTransform.ScaleY)" | |
To="1" Duration="00:00:.2" | |
EasingFunction="{StaticResource ExponentialEaseOut}"/> | |
</Storyboard> | |
<Storyboard x:Key="CloseStoryboard"> | |
<DoubleAnimation Storyboard.TargetName="PART_DropDown" | |
Storyboard.TargetProperty="(Grid.RenderTransform).(ScaleTransform.ScaleY)" | |
To="0" Duration="00:00:.2" | |
EasingFunction="{StaticResource ExponentialEaseOut}"/> | |
</Storyboard> | |
</ControlTemplate.Resources> | |
<controls:SmallPanel SnapsToDevicePixels="True"> | |
<Border Name="PART_Border" | |
Background="{TemplateBinding Background}" | |
BorderBrush="{TemplateBinding BorderBrush}" | |
BorderThickness="{TemplateBinding BorderThickness}" | |
SnapsToDevicePixels="True" | |
CornerRadius="{Binding Path=(helpers:ElementHelper.CornerRadius), RelativeSource={RelativeSource TemplatedParent}}" /> | |
<ToggleButton x:Name="PART_ToggleButton" | |
Template="{StaticResource ComboBoxToggleButton}" | |
Style="{x:Null}" | |
Focusable="False" | |
ClickMode="Release" | |
IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"/> | |
<TextBox Name="PART_EditableTextBox" | |
Template="{StaticResource ComboBoxTextBox}" | |
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" | |
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" | |
Margin="{TemplateBinding Padding}" | |
Focusable="True" | |
Foreground="{DynamicResource PrimaryTextSolidColorBrush}" | |
Text="{Binding Text,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}" | |
Background="{TemplateBinding Background}" | |
SelectionBrush="{DynamicResource WindowBorderBrushSolidColorBrush}" | |
IsReadOnly="True" Style="{x:Null}" /> | |
<TextBlock x:Name="PART_Watermark" | |
Text="{Binding Path=(helpers:ElementHelper.Watermark),RelativeSource={RelativeSource TemplatedParent}}" | |
Foreground="{DynamicResource RegularTextSolidColorBrush}" | |
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" | |
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" | |
FontSize="{StaticResource NormalFontSize}" | |
Margin="{TemplateBinding Padding}" | |
Background="Transparent" | |
TextTrimming="CharacterEllipsis" | |
IsHitTestVisible="False" | |
Padding="1,0" | |
Visibility="Collapsed"/> | |
<Popup x:Name="PART_Popup" | |
AllowsTransparency="True" | |
PlacementTarget="{Binding ElementName=PART_ToggleButton}" | |
IsOpen="{Binding IsDropDownOpen,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}" | |
Placement="Bottom" StaysOpen="False"> | |
<controls:SmallPanel x:Name="PART_DropDown" | |
MinWidth="{TemplateBinding FrameworkElement.ActualWidth}" | |
Margin="24,2,24,24" | |
MaxHeight="{TemplateBinding MaxDropDownHeight}" | |
RenderTransformOrigin=".5,0" | |
SnapsToDevicePixels="True"> | |
<controls:SmallPanel.RenderTransform> | |
<ScaleTransform ScaleY="0"/> | |
</controls:SmallPanel.RenderTransform> | |
<Border | |
Name="PART_DropDownBorder" | |
Background="{TemplateBinding Background}" | |
BorderBrush="{TemplateBinding BorderBrush}" | |
BorderThickness="{TemplateBinding BorderThickness}" | |
SnapsToDevicePixels="True" | |
CornerRadius="{Binding Path=(helpers:ElementHelper.CornerRadius),RelativeSource={RelativeSource TemplatedParent}}" | |
UseLayoutRounding="True" | |
Effect="{StaticResource PopupShadowDepth}"/> | |
<Grid ClipToBounds="False" | |
Margin="0,8" > | |
<Grid.RowDefinitions> | |
<RowDefinition Height="Auto"/> | |
<RowDefinition Height="Auto"/> | |
<RowDefinition/> | |
</Grid.RowDefinitions> | |
<TextBox x:Name="PART_TextBox" | |
Margin="6,0" | |
helpers:ElementHelper.Watermark="{Binding SearchWatermark,RelativeSource={RelativeSource TemplatedParent}}"/> | |
<CheckBox x:Name="PART_SelectAll" | |
Grid.Row="1" | |
Margin="5,4" | |
Visibility="{TemplateBinding IsSelectAllActive,Converter={StaticResource bool2VisibilityConverter}}" | |
Content="{TemplateBinding SelectAllContent}"/> | |
<controls:MultiSelectListBox x:Name="PART_Selector" | |
Grid.Row="2" | |
BorderThickness="1,0,1,0" | |
DisplayMemberPath="{TemplateBinding DisplayMemberPath}" | |
SelectedValuePath="{TemplateBinding SelectedValuePath}" | |
MinHeight="{TemplateBinding MinHeight}" | |
ItemsSource="{TemplateBinding ItemsSource}" | |
SelectionMode="Multiple" | |
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" | |
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" | |
ItemContainerStyle="{StaticResource DefaultMultiSelectComboBoxItem}" | |
ScrollViewer.HorizontalScrollBarVisibility="{Binding ScrollViewer.HorizontalScrollBarVisibility, RelativeSource={RelativeSource TemplatedParent}}" | |
ScrollViewer.CanContentScroll="{Binding ScrollViewer.CanContentScroll, RelativeSource={RelativeSource TemplatedParent}}" | |
BorderBrush="{TemplateBinding BorderBrush}"> | |
</controls:MultiSelectListBox> | |
<controls:MultiSelectListBox x:Name="PART_SearchSelector" | |
Grid.Row="2" | |
BorderThickness="1,0,1,0" | |
BorderBrush="{TemplateBinding BorderBrush}" | |
DisplayMemberPath="{TemplateBinding DisplayMemberPath}" | |
SelectedValuePath="{TemplateBinding SelectedValuePath}" | |
MinHeight="{TemplateBinding MinHeight}" | |
ItemsSource="{TemplateBinding ItemsSourceSearch}" | |
SelectionMode="Multiple" | |
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" | |
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" | |
ItemContainerStyle="{StaticResource DefaultMultiSelectComboBoxItem}" | |
ScrollViewer.HorizontalScrollBarVisibility="{Binding ScrollViewer.HorizontalScrollBarVisibility, RelativeSource={RelativeSource TemplatedParent}}" | |
ScrollViewer.CanContentScroll="{Binding ScrollViewer.CanContentScroll, RelativeSource={RelativeSource TemplatedParent}}" | |
Visibility="Collapsed"> | |
</controls:MultiSelectListBox> | |
</Grid> | |
</controls:SmallPanel> | |
</Popup> | |
</controls:SmallPanel> | |
<ControlTemplate.Triggers> | |
<Trigger SourceName="PART_ToggleButton" Property="IsChecked" Value="True"> | |
<Trigger.EnterActions> | |
<BeginStoryboard x:Name="BeginStoryboardOpenStoryboard" Storyboard="{StaticResource OpenStoryboard}" /> | |
</Trigger.EnterActions> | |
<Trigger.ExitActions> | |
<StopStoryboard BeginStoryboardName="BeginStoryboardOpenStoryboard" /> | |
</Trigger.ExitActions> | |
</Trigger> | |
<Trigger SourceName="PART_ToggleButton" Property="IsChecked" Value="False"> | |
<Trigger.EnterActions> | |
<BeginStoryboard x:Name="BeginStoryboardCloseStoryboard" Storyboard="{StaticResource CloseStoryboard}" /> | |
</Trigger.EnterActions> | |
<Trigger.ExitActions> | |
<StopStoryboard BeginStoryboardName="BeginStoryboardCloseStoryboard" /> | |
</Trigger.ExitActions> | |
</Trigger> | |
<Trigger Property="IsMouseOver" Value="True"> | |
<Setter Property="BorderBrush" TargetName="PART_Border" Value="{DynamicResource PrimaryNormalSolidColorBrush}"/> | |
</Trigger> | |
<Trigger SourceName="PART_Popup" Property="AllowsTransparency" Value="True"> | |
<Setter TargetName="PART_DropDownBorder" Property="Margin" Value="0,2,0,0" /> | |
</Trigger> | |
<Trigger Property="Text" Value=""> | |
<Setter Property="Visibility" TargetName="PART_Watermark" Value="Visible"/> | |
</Trigger> | |
<Trigger Property="Text" Value="{x:Null}"> | |
<Setter Property="Visibility" TargetName="PART_Watermark" Value="Visible"/> | |
</Trigger> | |
</ControlTemplate.Triggers> | |
</ControlTemplate> | |
</Setter.Value> | |
</Setter> | |
</Style> | |
</ResourceDictionary> |
4)创建 MultiSelectSearchComboBoxExample.xaml
代码如下:
<Grid> | |
<Grid.RowDefinitions> | |
<RowDefinition Height="Auto"/> | |
<RowDefinition/> | |
</Grid.RowDefinitions> | |
<Button Content="获取选中" | |
VerticalAlignment="Bottom" | |
HorizontalAlignment="Center" | |
Click="Button_Click" | |
Margin="0,20,0,0" | |
Style="{StaticResource SuccessPrimaryButton}"/> | |
<UniformGrid Columns="2" Rows="2" Grid.Row="1"> | |
<wd:MultiSelectionSearchComboBox | |
VerticalContentAlignment="Center" | |
HorizontalAlignment="Center" | |
ItemsSource="{Binding Drawings}" | |
DisplayMemberPath="Number" | |
SelectedValuePath="Index" | |
Width="200" Delimiter="," | |
IsSelectAllActive="True"> | |
<wd:MultiSelectionSearchComboBox.DataContext> | |
<vm:DrawingExampleVM/> | |
</wd:MultiSelectionSearchComboBox.DataContext> | |
</wd:MultiSelectionSearchComboBox> | |
<wd:MultiSelectionSearchComboBox | |
Name="MyMultiSelectionSearchComboBox2" | |
VerticalContentAlignment="Center" | |
HorizontalAlignment="Center" | |
ItemsSource="{Binding Drawings}" | |
DisplayMemberPath="Number" | |
SelectedValuePath="Index" | |
Width="200" Delimiter="^" | |
IsSelectAllActive="True" | |
wd:ElementHelper.Watermark="MultiSelectionSearchComboBox" | |
SearchWatermark="请输入搜索内容"> | |
<wd:MultiSelectionSearchComboBox.DataContext> | |
<vm:DrawingExampleVM/> | |
</wd:MultiSelectionSearchComboBox.DataContext> | |
</wd:MultiSelectionSearchComboBox> | |
</UniformGrid> | |
</Grid> |
5)创建 MultiSelectSearchComboBoxExample.xaml.cs
代码如下:
using System.Windows; | |
using System.Windows.Controls; | |
namespace WPFDevelopers.Samples.ExampleViews | |
{ | |
/// <summary> | |
/// MultiSelectComboBoxExample.xaml 的交互逻辑 | |
/// </summary> | |
public partial class MultiSelectSearchComboBoxExample : UserControl | |
{ | |
public MultiSelectSearchComboBoxExample() | |
{ | |
InitializeComponent(); | |
} | |
private void Button_Click(object sender, System.Windows.RoutedEventArgs e) | |
{ | |
WPFDevelopers.Controls.MessageBox.Show($"{MyMultiSelectionSearchComboBox2.Text} \r\n总共选中:{MyMultiSelectionSearchComboBox2.SelectedItems.Count} 条","选中内容",MessageBoxButton.OK,MessageBoxImage.Information); | |
} | |
} | |
} |
效果图