摘要:新增了三个属性:SelectedItemsExt所选择的项、ShowType枚举类型。SelectedItemsExt支持双向数据绑定,允许外部修改选中的项。ShowType定义了两种展示方式:Text:选择项以文本的形式展示。Tag:选择项按标签展示,多个标
增强 MultiSelectComboBox 带 Tag 显示和搜索功能的多选控件
控件名:MultiSelectComboBox
作 者:WPFDevelopersOrg - 驚鏵
原文链接[1]:https://github.com/WPFDevelopersOrg/WPFDevelopers
码云链接[2]:https://gitee.com/WPFDevelopersOrg/WPFDevelopers
框架支持.NET4 至 .NET8;
Visual Studio 2022;
MultiSelectComboBox中。欢迎各位开发者下载并体验。如果在使用过程中遇到任何问题,欢迎随时向我们反馈[3]。新增了三个属性:SelectedItemsExt所选择的项、ShowType枚举类型。SelectedItemsExt支持双向数据绑定,允许外部修改选中的项。
ShowType定义了两种展示方式:
Text:选择项以文本的形式展示。
Tag:选择项按标签展示,多个标签(带有删除按钮的标签)。
1. 修改 MultiSelectComboBox.cs新增方法 UpdateTags&CreateTag。
CreateTag创建Tag并将ListboxItem的Content赋值给Tag的Content。
UpdateTags先将Panel清空,然后重新循环SelectedItems调用CreateTag并创建Tag。
Tags_CloseTag关闭事件,关闭Tag时,将当前MultiSelectComboBoxItem的IsSelected设置为False。
using System;using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Threading;
using WPFDevelopers.Helpers;
namespace WPFDevelopers.Controls
{
[TemplatePart(Name = PART_Popup, Type = typeof(Popup))]
[TemplatePart(Name = PART_SimpleWrapPanel, Type = typeof(Panel))]
[TemplatePart(Name = CheckBoxTemplateName, Type = typeof(CheckBox))]
[TemplatePart(Name = TextBoxTemplateName, Type = typeof(TextBox))]
[TemplatePart(Name = ListBoxTemplateNameSearch, Type = typeof(ListBox))]
[TemplatePart(Name = DropDownScrollViewer, Type = typeof(ScrollViewer))]
[TemplatePart(Name = PART_ItemsPresenter, Type = typeof(ItemsPresenter))]
public class MultiSelectComboBox : ListBox
{
private const string PART_Popup = "PART_Popup";
private const string PART_SimpleWrapPanel = "PART_SimpleWrapPanel";
private const string CheckBoxTemplateName = "PART_SelectAll";
private const string TextBoxTemplateName = "PART_TextBox";
private const string ListBoxTemplateNameSearch = "PART_SearchSelector";
private const string DropDownScrollViewer = "DropDownScrollViewer";
private const string PART_ItemsPresenter = "PART_ItemsPresenter";
public static readonly DependencyProperty IsDropDownOpenProperty =
DependencyProperty.Register("IsDropDownOpen", typeof(bool), typeof(MultiSelectComboBox),
new PropertyMetadata(false));
public static readonly DependencyProperty MaxDropDownHeightProperty
= DependencyProperty.Register("MaxDropDownHeight", typeof(double), typeof(MultiSelectComboBox),
new PropertyMetadata(SystemParameters.PrimaryScreenHeight / 3));
public static readonly DependencyProperty SelectAllContentProperty =
DependencyProperty.Register("SelectAllContent", typeof(object), typeof(MultiSelectComboBox),
new PropertyMetadata(LanguageManager.Instance["SelectAll"]));
public static readonly DependencyProperty IsSelectAllActiveProperty =
DependencyProperty.Register("IsSelectAllActive", typeof(bool), typeof(MultiSelectComboBox),
public static readonly DependencyProperty DelimiterProperty =
DependencyProperty.Register("Delimiter", typeof(string), typeof(MultiSelectComboBox),
new PropertyMetadata(";"));
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(MultiSelectComboBox),
new PropertyMetadata(string.Empty, OnTextChanged));
public static readonly DependencyProperty SearchWatermarkProperty =
DependencyProperty.Register("SearchWatermark",
typeof(string),
typeof(MultiSelectComboBox),
new PropertyMetadata(string.Empty));
public static readonly DependencyProperty ItemsSourceSearchProperty =
DependencyProperty.Register("ItemsSourceSearch", typeof(IEnumerable), typeof(MultiSelectComboBox),
new PropertyMetadata);
public static readonly DependencyProperty IsSearchProperty =
DependencyProperty.Register("IsSearch", typeof(bool), typeof(MultiSelectComboBox),
new PropertyMetadata(true));
public static readonly DependencyProperty SelectedItemsExtProperty =
DependencyProperty.Register("SelectedItemsExt", typeof(IList), typeof(MultiSelectComboBox),
new FrameworkPropertyMetadata(new ArrayList,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal,
OnSelectedItemsExtChanged));
public static readonly DependencyProperty ShowTypeProperty =
DependencyProperty.Register("ShowType", typeof(ShowType), typeof(MultiSelectComboBox),
new PropertyMetadata(ShowType.Text));
private bool _ignoreTextValueChanged;
private Popup _popup;
private Panel _panel;
private ListBox _listBoxSearch;
private TextBox _textBox;
private ScrollViewer _scrollViewer;
private CheckBox _checkBox;
private string _theLastText;
private ItemsPresenter _itemsPresenter;
private List selectedItems;
private List selectedList;
private List selectedSearchList;
public bool IsDropDownOpen
{
get => (bool)GetValue(IsDropDownOpenProperty);
set => SetValue(IsDropDownOpenProperty, value);
}
[Bindable(true)]
[Category("Layout")]
[TypeConverter(typeof(LengthConverter))]
public double MaxDropDownHeight
{
get => (double)GetValue(MaxDropDownHeightProperty);
set => SetValue(MaxDropDownHeightProperty, value);
}
public object SelectAllContent
{
get => GetValue(SelectAllContentProperty);
set => SetValue(SelectAllContentProperty, value);
}
public bool IsSelectAllActive
{
get => (bool)GetValue(IsSelectAllActiveProperty);
set => SetValue(IsSelectAllActiveProperty, value);
}
public string Delimiter
{
get => (string)GetValue(DelimiterProperty);
set => SetValue(DelimiterProperty, value);
}
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public string SearchWatermark
{
get => (string)GetValue(SearchWatermarkProperty);
set => SetValue(SearchWatermarkProperty, value);
}
public IEnumerable ItemsSourceSearch
{
get => (IEnumerable)GetValue(ItemsSourceSearchProperty);
set => SetValue(ItemsSourceSearchProperty, value);
}
public bool IsSearch
{
get => (bool)GetValue(IsSearchProperty);
set => SetValue(IsSearchProperty, value);
}
public IList SelectedItemsExt
{
get => (IList)GetValue(SelectedItemsExtProperty);
set => SetValue(SelectedItemsExtProperty, value);
}
public ShowType ShowType
{
get => (ShowType)GetValue(ShowTypeProperty);
set => SetValue(ShowTypeProperty, value);
}
[DllImport(Win32.User32)]
private static extern IntPtr SetFocus(IntPtr hWnd);
private static void OnIsDropDownOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var MultiSelectComboBox = (MultiSelectComboBox)d;
if (!(bool)e.NewValue)
MultiSelectComboBox.Dispatcher.BeginInvoke(new Action( => { Mouse.Capture; }),
DispatcherPriority.Send);
}
private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is MultiSelectComboBoxItem;
}
protected override DependencyObject GetContainerForItemOverride
{
return new MultiSelectComboBoxItem;
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
if (SelectedItems != )
{
foreach (var item in e.AddedItems)
{
if (!SelectedItems.Contains(item))
{
SelectedItems.Add(item);
}
}
foreach (var item in e.RemovedItems)
{
SelectedItems.Remove(item);
}
SelectionChecked(this);
}
UpdateText;
}
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
base.OnItemsSourceChanged(oldValue, newValue);
if (_textBox != )
{
if (Items.Count > 0 && ItemsSource != )
{
IsSearch = true;
_textBox.Visibility = Visibility.Visible;
_textBox.TextChanged -= OnTextbox_TextChanged;
_textBox.TextChanged += OnTextbox_TextChanged;
}
else
IsSearch = false;
}
}
public override void OnApplyTemplate
{
base.OnApplyTemplate;
LanguageManager.Instance.PropertyChanged += Instance_PropertyChanged;
selectedList = new List;
selectedSearchList = new List;
selectedItems = new List;
_textBox = GetTemplateChild(TextBoxTemplateName) as TextBox;
_popup = GetTemplateChild(PART_Popup) as Popup;
if (_popup != )
{
_popup.Opened += OnPopup_Opened;
_popup.GotFocus += OnPopup_GotFocus;
}
_scrollViewer = GetTemplateChild(DropDownScrollViewer) as ScrollViewer;
_listBoxSearch = GetTemplateChild(ListBoxTemplateNameSearch) as ListBox;
_checkBox = GetTemplateChild(CheckBoxTemplateName) as CheckBox;
if (_checkBox != )
{
_checkBox.Checked -= OnCheckBox_Checked;
_checkBox.Unchecked -= OnCheckBox_Unchecked;
_checkBox.Checked += OnCheckBox_Checked;
_checkBox.Unchecked += OnCheckBox_Unchecked;
}
if (_listBoxSearch != )
{
_listBoxSearch.IsVisibleChanged -= OnListBoxSearch_IsVisibleChanged;
_listBoxSearch.IsVisibleChanged += OnListBoxSearch_IsVisibleChanged;
_listBoxSearch.SelectionChanged -= OnListBoxSearch_SelectionChanged;
_listBoxSearch.SelectionChanged += OnListBoxSearch_SelectionChanged;
}
_itemsPresenter = GetTemplateChild(PART_ItemsPresenter) as ItemsPresenter;
if (ShowType == ShowType.Tag)
{
AddHandler(Controls.Tag.CloseEvent, new RoutedEventHandler(Tags_Close));
_panel = GetTemplateChild(PART_SimpleWrapPanel) as Panel;
}
}
private void OnPopup_Opened(object sender, EventArgs e)
{
UpdateTags;
}
private void OnListBoxSearch_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!_listBoxSearch.IsVisible)
return;
if (e.RemovedItems.Count > 0)
{
{
if (selectedSearchList.Contains(item))
selectedSearchList.Remove(item);
if (_listBoxSearch.Items.Contains(item))
{
if (SelectedItems.Contains(item))
}
if (selectedList.Contains(item))
selectedList.Remove(item);
}
UpdateText;
SelectionChecked(_listBoxSearch);
}
if (e.AddedItems.Count > 0)
{
{
}
UpdateText;
}
}
private void OnListBoxSearch_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
UpdateIsChecked(_listBoxSearch);
}
private void UpdateIsChecked(ListBox listBox)
{
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 = ;
}
}
private void OnCheckBox_Unchecked(object sender, RoutedEventArgs e)
{
if (_listBoxSearch.Visibility == Visibility.Visible)
_listBoxSearch.UnselectAll;
else
UnselectAll;
}
private void OnCheckBox_Checked(object sender, RoutedEventArgs e)
{
_listBoxSearch.SelectAll;
else
SelectAll;
}
private void OnTextbox_TextChanged(object sender, TextChangedEventArgs e)
{
if (string.IsOrWhiteSpace(_theLastText))
_theLastText = _textBox.Text;
SearchText(_textBox.Text);
}
private void OnPopup_GotFocus(object sender, RoutedEventArgs e)
{
var source = (HwndSource)PresentationSource.FromVisual(_popup.Child);
if (source != )
{
SetFocus(source.Handle);
_textBox.Focus;
}
}
private void SearchText(string text)
{
if (string.IsOrWhiteSpace(text))
{
if (_listBoxSearch.Visibility != Visibility.Collapsed)
_listBoxSearch.Visibility = Visibility.Collapsed;
if (_scrollViewer.Visibility != Visibility.Visible)
{
_scrollViewer.Visibility = Visibility.Visible;
UpdateIsChecked(this);
}
}
else
{
if (_listBoxSearch.Visibility != Visibility.Visible)
_listBoxSearch.Visibility = Visibility.Visible;
if (_scrollViewer.Visibility != Visibility.Collapsed)
_scrollViewer.Visibility = Visibility.Collapsed;
var listSearch = new List;
foreach (var item in Items)
{
var str = GetPropertyValue(item);
if (string.IsOrWhiteSpace(str))
str = item.ToString;
if (!string.IsOrWhiteSpace(str))
if (str.ToUpperInvariant.Contains(text.ToUpperInvariant))
listSearch.Add(item);
}
foreach (var item in selectedList)
if (!listSearch.Contains(item))
ItemsSourceSearch = listSearch;
selectedItems.Clear;
foreach (var item in _listBoxSearch.Items)
{
if (!_listBoxSearch.SelectedItems.Contains(item))
_listBoxSearch.SelectedItems.Add(item);
}
}
}
private void SelectionChecked(ListBox listbox)
{
if (listbox.SelectedItems.Count > 0
&&
listbox.Items.Count == listbox.SelectedItems.Count)
{
}
else
{
&&
{
}
else
{
if (listbox.SelectedItems.Count == 0)
else
}
}
}
private string GetPropertyValue(object item)
{
if (string.IsOrEmpty(DisplayMemberPath))
{
if (item is ContentControl contentControl && contentControl.Content != )
{
return contentControl.Content.ToString ?? string.Empty;
}
return item?.ToString ?? string.Empty;
}
var result = string.Empty;
var nameParts = DisplayMemberPath.Split('.');
if (nameParts.Length == 1)
{
var property = item.GetType.GetProperty(DisplayMemberPath);
if (property != )
{
return (property.GetValue(item, ) ?? string.Empty).ToString;
}
}
return result.ToUpperInvariant;
}
private void Instance_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
SelectAllContent = LanguageManager.Instance["SelectAll"];
}
private void OnMultiSelectComboBoxItem_Unselected(object sender, RoutedEventArgs e)
{
if (_ignoreTextValueChanged) return;
_ignoreTextValueChanged = true;
UnselectAll;
_ignoreTextValueChanged = false;
UpdateText;
}
private void OnMultiSelectComboBoxItem_Selected(object sender, RoutedEventArgs e)
{
SelectAll;
UpdateText;
}
protected virtual void UpdateText
{
var newValue = string.Join(Delimiter, SelectedItems.Cast.Select(x => GetItemDisplayValue(x)));
if (string.IsOrWhiteSpace(Text) || !Text.Equals(newValue))
{
SetCurrentValue(TextProperty, newValue);
UpdateTags;
}
}
private void UpdateTags
{
if (_panel == ) return;
_panel.Children.Clear;
foreach (var item in SelectedItems)
{
if (ItemContainerGenerator.ContainerFromItem(item) is MultiSelectComboBoxItem multiSelectComboBoxItem)
CreateTag(item, multiSelectComboBoxItem);
else
CreateTag(item);
}
}
void CreateTag(object item, MultiSelectComboBoxItem multiSelectComboBoxItem = )
{
var tag = new Tag { Padding = new Thickness(2, 0, 0, 0) };
if (ItemsSource != )
{
var binding = new Binding(DisplayMemberPath) { Source = item };
tag.SetBinding(ContentControl.ContentProperty, binding);
}
else
{
if (multiSelectComboBoxItem != )
tag.Content = multiSelectComboBoxItem.Content;
}
tag.Tag = multiSelectComboBoxItem;
ElementHelper.SetCornerRadius(tag, new CornerRadius(3));
_panel.Children.Add(tag);
}
private void Tags_Close(object sender, RoutedEventArgs e)
{
var tag = (Tag)e.OriginalSource;
var multiSelectComboBoxItem = (MultiSelectComboBoxItem)tag.Tag;
multiSelectComboBoxItem.SetCurrentValue(IsSelectedProperty, false);
}
protected object GetItemDisplayValue(object item)
{
if (string.IsOrWhiteSpace(DisplayMemberPath))
{
var property = item.GetType.GetProperty("Content");
if (property != )
return property.GetValue(item, );
}
{
if (property != )
}
return item;
}
private static void OnSelectedItemsExtChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var multiSelectComboBox = d as MultiSelectComboBox;
if (multiSelectComboBox == ) return;
if (e.NewValue != )
{
var collection = e.NewValue as IList;
if (collection.Count <= 0) return;
multiSelectComboBox.SelectedItems.Clear;
foreach (var item in collection)
{
var name = multiSelectComboBox.GetPropertyValue(item);
object model = ;
if (!string.IsOrWhiteSpace(name))
model = multiSelectComboBox.ItemsSource.OfType.FirstOrDefault(h =>
multiSelectComboBox.GetPropertyValue(h) == name);
else
model = multiSelectComboBox.ItemsSource.OfType
.FirstOrDefault(h => h == item);
if (model != && !multiSelectComboBox.SelectedItems.Contains(item))
multiSelectComboBox.SelectedItems.Add(model);
}
multiSelectComboBox.UpdateText;
}
}
}
public enum ShowType
{
Text,
Tag
}
}
2. 修改 MultiSelectComboBox.xaml
SimpleWrapPanel子元素自动换行,Spacing="2" 设置元素之间的间距为2,并设置Visibility="Collapsed"初始时设置为Collapsed,当ShowType为Tag时才显示。
PART_EditableTextBox搜索输入框并设置Visibility属性与IsSearch绑定是否显示搜索框。
来源:opendotnet