多选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

posted @ 2021-05-11 14:31  wesson2019  阅读(333)  评论(0编辑  收藏  举报