多选ComboBox
xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WessonControl.Themes"
xmlns:control="clr-namespace:WessonControl.Controls">
<Style x:Key="DefaultCheckListItem" TargetType="{x:Type control:CheckListItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type control:CheckListItem}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<CheckBox Margin="2,1,1,1" IsChecked="{Binding IsChecked, RelativeSource={RelativeSource TemplatedParent}}"/>
<ContentPresenter Grid.Column="1" Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type control:CheckList}" BasedOn="{StaticResource {x:Type ListBox}}">
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
<Setter Property="ItemContainerStyle" Value="{StaticResource DefaultCheckListItem}"/>
</Style>
<ControlTemplate x:Key="ComboBoxToggleButton" TargetType="{x:Type ToggleButton}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="20" />
</Grid.ColumnDefinitions>
<Border x:Name="Border" Grid.ColumnSpan="2" CornerRadius="2" BorderThickness="1" BorderBrush="#FFCCCCCC">
</Border>
<Border Grid.Column="0" CornerRadius="2,0,0,2" Margin="1">
<Border.Background>
<SolidColorBrush Color="White"/>
</Border.Background>
</Border>
<Border Grid.Column="1" x:Name="Bg">
</Border>
<Path Grid.Column="1" x:Name="Arrow" HorizontalAlignment="Center" VerticalAlignment="Center" Data="M 0 0 L 4 4 L 8 0 Z">
<Path.Fill>
<SolidColorBrush Color="#FF444444"/>
</Path.Fill>
</Path>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" TargetName="Bg" Value="#FFB9B9B9"/>
<Setter Property="Opacity" TargetName="Bg" Value="0.6"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<!--编辑状态文本框样式-->
<Style TargetType="{x:Type TextBox}" x:Key="EditableTextBoxStyle">
<Setter Property="Margin" Value="1"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Background" Value="{x:Null}"/>
<Setter Property="MaxLength" Value="2048"/>
<Setter Property="ContextMenu" Value="{DynamicResource TextBoxContextMenu}" />
<Setter Property="Focusable" Value="True"/>
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="SnapsToDevicePixels" Value="True"></Setter>
<Style.Triggers>
</Style.Triggers>
</Style>
<Style x:Key="DefaultMultiComboBox" TargetType="{x:Type ComboBox}">
<Setter Property="IsEditable" Value="True"/>
<Setter Property="IsReadOnly" Value="True" />
<Setter Property="MinHeight" Value="26" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="ScrollViewer.CanContentScroll" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ComboBox}">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="MouseOver" />
<VisualState x:Name="Disabled">
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="EditStates">
<VisualState x:Name="Editable">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
Storyboard.TargetName="PART_EditableTextBox">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
Storyboard.TargetName="ContentSite">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Hidden}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Uneditable" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ToggleButton x:Name="ToggleButton" Template="{StaticResource ComboBoxToggleButton}"
Grid.Column="2" Focusable="false" ClickMode="Press"
IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"/>
<ContentPresenter x:Name="ContentSite"
IsHitTestVisible="False" Margin="3,3,23,3" VerticalAlignment="Stretch" HorizontalAlignment="Left"
Content="{TemplateBinding SelectionBoxItem}"
ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}" >
</ContentPresenter>
<Grid Grid.Column="1" Margin="2 0 0 0">
<TextBox x:Name="PART_EditableTextBox" Style="{StaticResource EditableTextBoxStyle}"
Margin="3,3,23,3" Background="Transparent" Visibility="Hidden" IsReadOnly="{TemplateBinding IsReadOnly}"
Text="{Binding Path=SelectedText, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" />
</Grid>
<!--弹出多选列表-->
<Popup x:Name="Popup" Placement="Bottom" IsOpen="{TemplateBinding IsDropDownOpen}"
AllowsTransparency="True" Focusable="False" PopupAnimation="Slide">
<Grid x:Name="DropDown" SnapsToDevicePixels="True"
MaxHeight="{TemplateBinding MaxDropDownHeight}"
Width="{TemplateBinding ActualWidth}">
<control:CheckList x:Name="PART_ComboBoxItemListBox" ItemsSource="{TemplateBinding ItemsSource}"
SplitChar=";">
</control:CheckList>
</Grid>
</Popup>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type control:MultiCheckComboBox}" BasedOn="{StaticResource DefaultMultiComboBox}"/>
</ResourceDictionary>
cs
[TemplatePart(Name = PART_ComboBoxItemListBox, Type = typeof(CheckList))]
public partial class MultiCheckComboBox : ComboBox
{
// 控件模板和样式要加在资源字典中以便引用
public const string PART_ComboBoxItemListBox = "PART_ComboBoxItemListBox";
private CheckList _listbox;
static MultiCheckComboBox()
{
// 设置样式键值
DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiCheckComboBox), new FrameworkPropertyMetadata(typeof(MultiCheckComboBox)));
}
public MultiCheckComboBox() { }
#region SelectedText
/// <summary>
/// 显示内容
/// </summary>
public string SelectedText
{
get { return (string)GetValue(SelectedTextProperty); }
set { SetValue(SelectedTextProperty, value); }
}
public static readonly DependencyProperty SelectedTextProperty =
DependencyProperty.Register("SelectedText", typeof(string), typeof(MultiCheckComboBox),
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnSelectedTextPropertyChanged)));
private static void OnSelectedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is MultiCheckComboBox obj)
{
obj.OnSelectedTextChanged((string)e.NewValue);
}
}
private void OnSelectedTextChanged(string displayTxt)
{
SetText(displayTxt);
SelectedArrayText = null;
SelectedArrayText = ToArray().ToList();
}
#endregion
/// <summary>
/// 显示内容的数组形式
/// </summary>
public IEnumerable<string> SelectedArrayText
{
get { return (IEnumerable<string>)GetValue(SelectedArrayTextProperty); }
set { SetValue(SelectedArrayTextProperty, value); }
}
public static readonly DependencyProperty SelectedArrayTextProperty =
DependencyProperty.Register("SelectedArrayText", typeof(IEnumerable<string>), typeof(MultiCheckComboBox), new PropertyMetadata(null));
/// <summary>
/// 显示的时候会执行,多用于复杂的控件。
/// 一般在Style中使用TemplateBinding即可。
/// 有可能出现 属性更改通知 早于 OnApplyTemplate执行的问题
/// </summary>
public override void OnApplyTemplate()
{
// 获取Style中的控件
_listbox = GetTemplateChild(PART_ComboBoxItemListBox) as CheckList;
if (_listbox == null)
{
_listbox = new CheckList();
AddChild(_listbox);
}
if (_listbox != null)
{
_listbox.SelectedTextChanged -= Listbox_SelectedTextChanged;
_listbox.SelectedTextChanged += Listbox_SelectedTextChanged;
}
base.OnApplyTemplate();
}
private void Listbox_SelectedTextChanged(object sender, RoutedEventArgs e)
{
SelectedText = e.OriginalSource.ToString();
}
/// <summary>
/// 多个字符串组形式
/// </summary>
/// <returns></returns>
public string[] ToArray()
{
if (_listbox == null || string.IsNullOrWhiteSpace(_listbox.SelectedText))
{
return new string[] { };
}
return _listbox.SelectedText.Split(_listbox.SplitChar);
}
/// <summary>
/// 设置显示内容
/// </summary>
/// <param name="displayTxt">文本形式</param>
public void SetText(string displayTxt)
{
_listbox?.SetSelectedText(displayTxt);
}
/// <summary>
/// 设置显示内容
/// </summary>
/// <param name="item">显示项</param>
public void SetText(CheckListItem item)
{
_listbox.SetSelectedItem(item);
}
}
public class CheckListItem : ListBoxItem
{
static CheckListItem()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CheckListItem), new FrameworkPropertyMetadata(typeof(CheckListItem)));
}
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register("IsChecked", typeof(bool), typeof(CheckListItem), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnChanged)));
public static readonly RoutedEvent CheckedChangedEvent = EventManager.RegisterRoutedEvent("CheckedChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(CheckListItem));
public event RoutedEventHandler CheckedChanged
{
add
{
AddHandler(CheckedChangedEvent, value);
}
remove
{
RemoveHandler(CheckedChangedEvent, value);
}
}
private static void OnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is CheckListItem obj)
{
obj.OnCheckedChanged();
}
}
public void OnCheckedChanged()
{
var args = new RoutedEventArgs(CheckedChangedEvent, this);
RaiseEvent(args);
}
}
public class CheckList : ListBox
{
static CheckList()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CheckList), new FrameworkPropertyMetadata(typeof(CheckList)));
}
#region SelectedText
/// <summary>
/// 已选择的内容
/// </summary>
public string SelectedText
{
get { return (string)GetValue(SelectedTextProperty); }
set { SetValue(SelectedTextProperty, value); }
}
public static readonly DependencyProperty SelectedTextProperty =
DependencyProperty.Register("SelectedText", typeof(string), typeof(CheckList),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnSelectedTextPropertyChanged)));
private static void OnSelectedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is CheckList obj)
{
obj.OnSelectedTextChanged(e.NewValue.ToString());
}
}
private void OnSelectedTextChanged(string text)
{
var args = new RoutedEventArgs(SelectedTextChangedEvent, text);
RaiseEvent(args);
}
public static readonly RoutedEvent SelectedTextChangedEvent = EventManager.RegisterRoutedEvent("SelectedTextChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(CheckList));
public event RoutedEventHandler SelectedTextChanged
{
add { AddHandler(SelectedTextChangedEvent, value); }
remove { RemoveHandler(SelectedTextChangedEvent, value); }
}
#endregion
#region SplitChar
/// <summary>
/// 分隔符
/// </summary>
public char SplitChar
{
get { return (char)GetValue(SplitCharProperty); }
set { SetValue(SplitCharProperty, value); }
}
public static readonly DependencyProperty SplitCharProperty =
DependencyProperty.Register("SplitChar", typeof(char), typeof(CheckList), new PropertyMetadata(' '));
#endregion
protected override DependencyObject GetContainerForItemOverride()
{
return new CheckListItem();
}
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
(element as CheckListItem).CheckedChanged += CheckList_CheckedChanged;
base.PrepareContainerForItemOverride(element, item);
}
private void CheckList_CheckedChanged(object sender, RoutedEventArgs e)
{
var item = sender as CheckListItem;
bool isChecked = item.IsChecked;
string selected = item.Content.ToString();
string temp = SelectedText;
if (isChecked)
{
if (!Regex.IsMatch(SplitChar + SelectedText + SplitChar, SplitChar + selected + SplitChar))
{
temp += SplitChar + selected;
}
}
else
{
temp = Regex.Replace(SplitChar + temp + SplitChar, SplitChar + selected + SplitChar, SplitChar.ToString());
}
SelectedText = temp.Trim(SplitChar);
}
internal void SetSelectedText(string displayTxt)
{
if (ItemsSource == null)
{
return;
}
// 确保显示的文本是合法的
List<string> rslt = new List<string>();
var arr = displayTxt.Split(SplitChar);
var list = ItemsSource.OfType<CheckListItem>();
foreach (var item in list)
{
if (arr.Contains(item.Content.ToString()))
{
item.IsChecked = true;
rslt.Add(item.Content.ToString());
}
}
SelectedText = string.Join(SplitChar.ToString(), rslt.ToArray());
}
internal void SetSelectedItem(CheckListItem selectedItem)
{
var list = this.ItemsSource.OfType<CheckListItem>();
foreach (var item in list)
{
if (item.Content.Equals(selectedItem.Content))
{
item.IsChecked = true;
SelectedText = selectedItem.Content.ToString();
break;
}
}
}
}
demo
<wesson:MultiCheckComboBox x:Name="cmb" />
var data = from p in data
select new CheckListItem
{
IsChecked = true,
Content = p,
};
cmbExtend.ItemsSource = data;
cmbExtend.SetText(displayText);
prism
<wesson:MultiCheckComboBox x:Name="MultiCheckComboBoxSpaceType" Margin="5" Height="26"
ItemsSource="{Binding PropSetEntity.SpaceTypeSource}"
SelectedText="{Binding PropSetEntity.SpaceTypeText}"
SelectedArrayText="{Binding PropSetEntity.SelectedArrayText, Mode=TwoWay}">
</wesson:MultiCheckComboBox>
可多选ComboBox的简易实现
WPF中实现多选ComboBox控件
WPF 自定义ComboBox样式,自定义多选控件
WPF自定义控件与样式(8)-ComboBox与自定义多选控件MultComboBox