自定义WPF 多选Combobx

1.功能

1.1 实现功能

先看效果:

在这里插入图片描述

  • 1、对其中鼠标悬浮、选中的样式进行统一
  • 2、对其中滚动条样式进行全局统一配置
  • 3、对控件的Border Color 进行全局统一配置
  • 4、绑定数据时设置已选中项
  • 5、Popup 宽度根据内容自适应
  • 6、MutliCombobox可设置已选中项展示样式(横向/纵向)
1.2 全局配置
<!--默认Border Color-->
<SolidColorBrush x:Key="DefaultBrush" Color="#eaeaea"></SolidColorBrush>
<!--默认全局颜色-->
<SolidColorBrush x:Key="DefaultBackground" Color="#1ab394"></SolidColorBrush>
<!--鼠标悬浮背景颜色-->
<SolidColorBrush x:Key="MouseOverBackground" Color="#46e6c6"></SolidColorBrush>
<!--鼠标选中背景颜色-->
<SolidColorBrush x:Key="SelectedBackground" Color="#1ab394"></SolidColorBrush>

2. Combobox

2.1 Style
  <ComboBox.Style>
        <Style TargetType="{x:Type ComboBox}">
            <Setter Property="BorderBrush" Value="{StaticResource DefaultBrush}"></Setter>
            <Setter Property="ItemContainerStyle">
                <Setter.Value>
                    <Style TargetType="ComboBoxItem">
                        <Setter Property="Height" Value="20"/>
                        <Setter Property="Template">
                            <Setter.Value>
                                <ControlTemplate  TargetType="{x:Type ComboBoxItem}">
                                    <Grid Height="{TemplateBinding Height}" Width="{TemplateBinding Width}">
                                        <Border x:Name="_borderbg" Background="Transparent"/>
                                        <ContentPresenter ContentSource="{Binding Source}" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="3 0 3 0"></ContentPresenter>
                                        <Border x:Name="_border" Background="White" Opacity="0"/>
                                    </Grid>
                                    <ControlTemplate.Triggers>
                                        <Trigger Property="IsSelected" Value="true">
                                            <Setter TargetName="_borderbg" Property="Background" Value="{StaticResource SelectedBackground}" />
                                        </Trigger>
                                        <MultiTrigger>
                                            <MultiTrigger.Conditions>
                                                <Condition Property="IsSelected" Value="false"/>
                                                <Condition Property="IsMouseOver" Value="true"/>
                                            </MultiTrigger.Conditions>
                                            <Setter TargetName="_borderbg" Property="Background" Value="{StaticResource MouseOverBackground}" />
                                        </MultiTrigger>
                                    </ControlTemplate.Triggers>
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                    </Style>
                </Setter.Value>
            </Setter>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ComboBox}">
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="0.7*"/>
                                <ColumnDefinition Width="0.3*" MaxWidth="30"/>
                            </Grid.ColumnDefinitions>
                            <Border  Grid.Column="0" Grid.ColumnSpan="2" BorderThickness="1" BorderBrush="{TemplateBinding BorderBrush}" CornerRadius="1,0,0,1"/>
                            <ContentPresenter HorizontalAlignment="Left" Margin="3,3,0,3" x:Name="ContentSite" VerticalAlignment="Center"
                                              Content="{TemplateBinding SelectionBoxItem}" 
                                              ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}" 
                                              ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}" IsHitTestVisible="False"/>

                            <!--ToggleButton 已数据绑定到 ComboBox 本身以切换 IsDropDownOpen-->
                            <ToggleButton Grid.Column="0" Grid.ColumnSpan="2" x:Name="ToggleButton" 
                                          Template="{StaticResource ComboBoxToggleButton}" Focusable="false" 
                                          IsChecked="{Binding Path=IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" 
                                          ClickMode="Press"/>
                            <!--必须将 TextBox 命名为 PART_EditableTextBox,否则 ComboBox 将无法识别它-->
                            <TextBox Visibility="Hidden" BorderThickness="0"   
                                       Margin="2 0 0 0" x:Name="PART_EditableTextBox"  
                                       VerticalAlignment="Center" Focusable="True" 
                                       Background="Transparent" IsReadOnly="{TemplateBinding IsReadOnly}"/>

                            <!--Popup 可显示 ComboBox 中的项列表。IsOpen 已数据绑定到通过 ComboBoxToggleButton 来切换的 IsDropDownOpen-->
                            <Popup IsOpen="{TemplateBinding IsDropDownOpen}" Placement="Bottom" x:Name="Popup" Focusable="False" AllowsTransparency="True" PopupAnimation="Slide">
                                <Grid MaxHeight="150" MinWidth="{Binding ActualWidth, RelativeSource={RelativeSource TemplatedParent}}" x:Name="DropDown" SnapsToDevicePixels="True">
                                    <Border x:Name="DropDownBorder"  BorderBrush="#e8e8e8" BorderThickness="1 0 1 1"/>
                                    <ScrollViewer Padding="0 0 12 0" Margin="1" SnapsToDevicePixels="True" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" CanContentScroll="True">
                                        <!--StackPanel 用于显示子级,方法是将 IsItemsHost 设置为 True-->
                                        <StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Contained" Background="White"/>
                                    </ScrollViewer>
                                </Grid>
                            </Popup>
                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsEditable" Value="True">
                                <Setter TargetName="PART_EditableTextBox" Property="Visibility" Value="Visible" />
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ComboBox.Style>

3. MutliCombobox

3.1 控件代码
3.1.1 Style
 <ComboBox.Style>
        <!--MultiComboBox普通样式-->
        <Style TargetType="{x:Type ComboBox}">
            <Setter Property="Width" Value="200" />
            <Setter Property="HorizontalContentAlignment" Value="Stretch" />
            <Setter Property="VerticalContentAlignment" Value="Center" />
            <Setter Property="SnapsToDevicePixels" Value="True" />
            <Setter Property="MaxDropDownHeight" Value="400" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ComboBox}">
                        <Grid>
                            <Border x:Name="Bg" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"  
                                    Width="{TemplateBinding Width}" 
                                    Height="{TemplateBinding Height}" 
                                    BorderBrush="{StaticResource DefaultBrush}" BorderThickness="1"  >
                                <Grid>
                                    <Grid x:Name="PART_InnerGrid" Margin="0">
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="*" />
                                            <ColumnDefinition Width="0.3*" MaxWidth="30" />
                                        </Grid.ColumnDefinitions>
                                        <ListBox ItemsSource="{Binding CheckedItems,Mode=TwoWay,RelativeSource={RelativeSource TemplatedParent}}" 
                                                 SelectionMode="Multiple" BorderThickness="0" 
                                                 ScrollViewer.VerticalScrollBarVisibility="Disabled">
                                            <ListBox.ItemsPanel>
                                                <ItemsPanelTemplate>
                                                    <VirtualizingStackPanel Orientation="{Binding Path=CheckedOrientation, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ComboBox}}" VirtualizingStackPanel.IsVirtualizing="True" />
                                                </ItemsPanelTemplate>
                                            </ListBox.ItemsPanel>
                                            <ListBox.ItemContainerStyle>
                                                <Style TargetType="ListBoxItem">
                                                    <Setter Property="HorizontalAlignment" Value="Left"/>
                                                    <Setter Property="BorderThickness" Value="0"/>
                                                    <Setter Property="IsSelected" Value="True"/>
                                                    <Setter Property="Template">
                                                        <Setter.Value>
                                                            <ControlTemplate TargetType="ListBoxItem">
                                                                <CheckBox BorderThickness="0" 
                                                                          VerticalAlignment="Center" HorizontalAlignment="Center"  
                                                                          Content="{Binding Text}" 
                                                                          IsChecked="true"
                                                                          Click="CheckBox_Click"
                                                                          />
                                                            </ControlTemplate>
                                                        </Setter.Value>
                                                    </Setter>
                                                </Style>
                                            </ListBox.ItemContainerStyle>
                                        </ListBox>

                                        <!--下拉按钮-->
                                        <ToggleButton x:Name="PART_DropDownToggle" IsTabStop="False"  
                                         IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
                                         Grid.Column="1" Template="{StaticResource ComboBoxToggleButton}" />
                                    </Grid>
                                </Grid>
                            </Border>

                            <!--Popup 可显示 ComboBox 中的项列表。IsOpen 已数据绑定到通过 ComboBoxToggleButton 来切换的 IsDropDownOpen-->
                            <Popup IsOpen="{TemplateBinding IsDropDownOpen}" Placement="Bottom" 
                                   x:Name="Popup" Focusable="False" AllowsTransparency="True" PopupAnimation="Slide">
                                <Grid MaxHeight="150" MinWidth="{Binding ActualWidth, RelativeSource={RelativeSource TemplatedParent}}" 
                                      x:Name="DropDown" SnapsToDevicePixels="True">
                                    <ListBox SelectionMode="Multiple" BorderThickness="1 0 1 1" Background="White" Padding="0 0 12 0"
                                             ItemsSource="{Binding MultiItemSource,RelativeSource={RelativeSource TemplatedParent}}"
                                             MaxHeight="{TemplateBinding MaxDropDownHeight}" BorderBrush="{StaticResource DefaultBrush}"
                                             SelectionChanged="ListBoxItems_SelectionChanged">
                                        <ListBox.ItemContainerStyle>
                                            <Style  TargetType="ListBoxItem">
                                                <Setter Property="Template">
                                                    <Setter.Value>
                                                        <ControlTemplate TargetType="{x:Type ListBoxItem}" >
                                                            <Grid  Height="22">
                                                                <Border x:Name="bg" BorderBrush="{StaticResource DefaultBrush}" BorderThickness="0"/>
                                                                <ContentPresenter x:Name="content"  />
                                                                <Border Background="White"  Opacity="0"/>
                                                            </Grid>
                                                            <ControlTemplate.Triggers>
                                                                <Trigger Property="IsSelected" Value="True">
                                                                    <Setter  TargetName="bg"  Property="Background" Value="{StaticResource SelectedBackground}" />
                                                                </Trigger>
                                                                <MultiTrigger>
                                                                    <MultiTrigger.Conditions>
                                                                        <Condition Property="IsMouseOver" Value="True" />
                                                                        <Condition Property="IsSelected" Value="False"/>
                                                                    </MultiTrigger.Conditions>
                                                                    <Setter TargetName="bg" Property="Background" Value="{StaticResource MouseOverBackground}" />
                                                                    <Setter TargetName="bg" Property="Opacity" Value="0.7"/>
                                                                </MultiTrigger>
                                                                <Trigger Property="IsEnabled" Value="False">
                                                                    <Setter TargetName="bg" Property="Opacity" Value="0.3" />
                                                                    <Setter Property="Foreground" Value="Gray" />
                                                                </Trigger>
                                                                <DataTrigger Binding="{Binding IsChecked}" Value="True">
                                                                    <Setter Property="IsSelected" Value="True"></Setter>
                                                                </DataTrigger>
                                                            </ControlTemplate.Triggers>
                                                        </ControlTemplate>
                                                    </Setter.Value>
                                                </Setter>
                                            </Style>
                                        </ListBox.ItemContainerStyle>
                                        <ListBox.ItemTemplate>
                                            <DataTemplate>
                                                <Grid>
                                                    <CheckBox x:Name="chk" VerticalAlignment="Center"  
                                                              Foreground="{Binding Foreground,RelativeSource={RelativeSource AncestorType=ListBoxItem}}" 
                                                              IsChecked="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem},Path=IsSelected,Mode=TwoWay}"  
                                                              Content="{Binding Path=Text}"/>
                                                </Grid>
                                                <DataTemplate.Triggers>
                                                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem},Path=IsSelected}" Value="True">
                                                        <Setter TargetName="chk" Property="IsChecked" Value="True"/>
                                                    </DataTrigger>
                                                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem},Path=IsSelected}" Value="False">
                                                        <Setter TargetName="chk" Property="IsChecked" Value="False"/>
                                                    </DataTrigger>
                                                </DataTemplate.Triggers>
                                            </DataTemplate>
                                        </ListBox.ItemTemplate>
                                    </ListBox>
                                </Grid>
                            </Popup>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ComboBox.Style>
3.1.2 后台代码
   /// <summary>
   /// MultiComboBox.xaml 的交互逻辑
   /// </summary>
   public partial class MultiComboBox : System.Windows.Controls.ComboBox, INotifyPropertyChanged
   {
       public MultiComboBox()
       {
           InitializeComponent();
       }

       #region Fields

       /// <summary>
       /// 选中项列表
       /// </summary>
       private ObservableCollection<MultiComboBoxItem> CheckedItemsProperty = new ObservableCollection<MultiComboBoxItem>();

       [Bindable(true)]
       public ObservableCollection<MultiComboBoxItem> CheckedItems
       {
           get { return CheckedItemsProperty; }
           set
           {
               CheckedItemsProperty = value;
               RaisePropertyChanged("CheckedItems");
           }
       }

       /// <summary>
       /// 展示列表
       /// </summary>
       private ObservableCollection<MultiComboBoxItem> MultiItemSourceProperty = new ObservableCollection<MultiComboBoxItem>();

       [Bindable(true)]
       public ObservableCollection<MultiComboBoxItem> MultiItemSource
       {
           get { return MultiItemSourceProperty; }
           set
           {
               MultiItemSourceProperty = value;
               RaisePropertyChanged("MultiItemSource");
           }
       }

       [Bindable(true)]
       public Orientation CheckedOrientation
       {
           get { return (Orientation)GetValue(OrientationProperty); }
           set
           {
               SetValue(OrientationProperty, value);
           }
       }

       public static readonly DependencyProperty OrientationProperty =
           DependencyProperty.Register("CheckedOrientation",
               typeof(Orientation),
               typeof(MultiComboBox),
               new PropertyMetadata(Orientation.Horizontal));
       #endregion

       #region Method

       /// <summary>
       /// 设置数据源
       /// </summary>
       /// <param name="source"></param>
       public void SetSource(IEnumerable<MultiComboBoxItem> source)
       {
           MultiItemSource = new ObservableCollection<MultiComboBoxItem>(source);

           if (MultiItemSource.Any(x => x.IsChecked))
               CheckedItems = new ObservableCollection<MultiComboBoxItem>(source.Where(x => x.IsChecked).ToList());
       }

       public event PropertyChangedEventHandler PropertyChanged;
       protected void RaisePropertyChanged(string propertyName)
       {
           PropertyChangedEventHandler handler = PropertyChanged;
           if (handler != null)
           {
               handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
           }
       }
       #endregion

       #region Event

       /// <summary>
       /// 下拉列表选中变更
       /// </summary>
       /// <param name="sender"></param>
       /// <param name="e"></param>
       private void ListBoxItems_SelectionChanged(object sender, SelectionChangedEventArgs e)
       {
           foreach (var item in e.AddedItems)
           {
               MultiComboBoxItem datachk = item as MultiComboBoxItem;
               datachk.IsChecked = true;
               if (CheckedItems.IndexOf(datachk) < 0)
                   CheckedItems.Add(datachk);
           }

           foreach (var item in e.RemovedItems)
           {
               MultiComboBoxItem datachk = item as MultiComboBoxItem;
               datachk.IsChecked = false;
               CheckedItems.Remove(datachk);
           }
       }

       /// <summary>
       /// 复选框取消选中
       /// </summary>
       /// <param name="sender"></param>
       /// <param name="e"></param>
       private void CheckBox_Click(object sender, RoutedEventArgs e)
       {
           var ckb = (CheckBox)sender;
           ListBoxItem cp = ckb.TemplatedParent as ListBoxItem;
           MultiComboBoxItem item = cp.Content as MultiComboBoxItem;
           item.IsChecked = false;
           CheckedItems.Remove(item);
       }
       #endregion
   }
3.2 调用方法
  • XAML
<fcode:MultiComboBox x:Name="mtcbbTest" ></fcode:MultiComboBox> 
  • 后台代码
//设置数据源
mtcbbTest.SetSource(source);

//获取已选中项
mtcbbTest.CheckedItems;

4 代码资源下载

本文代码资源已上传:https://download.csdn.net/download/johnson55925/10800128

项目开源地址:
https://gitee.com/fcode_me/FCODE_DEMO

WPF中实现多选下拉框可以使用ComboBox和ListBox结合实现,然后使用MVVM模式来实现数据绑定。以下是实现的步骤: 1. 定义一个数据模型,用于存储下拉框的选项数据和选中状态。 ```csharp public class MultiSelectItem<T> { public T Item { get; set; } public bool IsSelected { get; set; } } ``` 2. 在ViewModel中,定义一个集合用于存储选项数据,并且提供一个属性用于绑定到ComboBox中的ItemsSource,以及提供一个属性用于绑定到ListBox中的ItemsSource。 ```csharp public class MainViewModel : INotifyPropertyChanged { private List<MultiSelectItem<string>> _items; public MainViewModel() { _items = new List<MultiSelectItem<string>>() { new MultiSelectItem<string>() {Item = "Item 1", IsSelected = false}, new MultiSelectItem<string>() {Item = "Item 2", IsSelected = false}, new MultiSelectItem<string>() {Item = "Item 3", IsSelected = false}, new MultiSelectItem<string>() {Item = "Item 4", IsSelected = false}, new MultiSelectItem<string>() {Item = "Item 5", IsSelected = false}, }; } public List<MultiSelectItem<string>> Items { get { return _items; } set { _items = value; OnPropertyChanged(nameof(Items)); } } public IEnumerable<string> SelectedItems { get { return _items.Where(x => x.IsSelected).Select(x => x.Item); } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } ``` 3. 在View中,使用ComboBox和ListBox来实现多选下拉框。ComboBox用于展示选中的选项,ListBox用于展示所有的选项。 ```xml <ComboBox ItemsSource="{Binding SelectedItems}"> <ComboBox.Style> <Style TargetType="ComboBox"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ComboBox"> <StackPanel Orientation="Vertical"> <TextBlock Text="Selected Items:" /> <ItemsControl ItemsSource="{Binding SelectedItems}"> <ItemsControl.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding}" /> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </StackPanel> </ControlTemplate> </Setter.Value> </Setter> </Style> </ComboBox.Style> </ComboBox> <ListBox ItemsSource="{Binding Items}"> <ListBox.ItemTemplate> <DataTemplate> <CheckBox Content="{Binding Item}" IsChecked="{Binding IsSelected}" /> </DataTemplate> </ListBox.ItemTemplate> </ListBox> ``` 以上就是使用MVVM模式实现WPF多选下拉框的步骤。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值