基于WPF实现多选下拉控件的示例代码
驚鏵 人气:0WPF 实现多选下拉控件
框架使用.NET40
;
Visual Studio 2022
;
创建控件 MultiSelectComboBox
继承 ListBox
。
- 依赖属性
IsSelectAllActive
是否支持显示全选,默认false
。 - 依赖属性
SelectAllContent
全选控件的Content
,默认显示全选
。 - 依赖属性
Delimiter
分隔符,默认显示;
。 - 依赖属性
Text
显示选择的所有Item
。 - 其他与
ComboBox
依赖属性一致。
实现代码
1) MultiSelectComboBox.xaml
代码如下:
using System; using System.ComponentModel; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Input; using System.Windows.Threading; namespace WPFDevelopers.Controls { public class MultiSelectComboBox : ListBox { private const string PART_Popup = "PART_Popup"; private const string PART_CheckBoxAll = "PART_CheckBoxAll"; 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("全选")); public static readonly DependencyProperty IsSelectAllActiveProperty = DependencyProperty.Register("IsSelectAllActive", typeof(bool), typeof(MultiSelectComboBox), new PropertyMetadata(false)); 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)); private bool _ignoreTextValueChanged; private MultiSelectComboBoxItem _multiSelectComboBoxItem; private Popup _popup; 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); } private static void OnIsDropDownOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var MultiSelectComboBox = (MultiSelectComboBox)d; if (!(bool)e.NewValue) MultiSelectComboBox.Dispatcher.BeginInvoke(new Action(() => { Mouse.Capture(null); }), 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) { UpdateText(); base.OnSelectionChanged(e); } public override void OnApplyTemplate() { base.OnApplyTemplate(); _popup = GetTemplateChild(PART_Popup) as Popup; _multiSelectComboBoxItem = GetTemplateChild(PART_CheckBoxAll) as MultiSelectComboBoxItem; _multiSelectComboBoxItem.Selected += _MultiSelectComboBoxItem_Selected; _multiSelectComboBoxItem.Unselected += _MultiSelectComboBoxItem_Unselected; } private void _MultiSelectComboBoxItem_Unselected(object sender, RoutedEventArgs e) { if (_ignoreTextValueChanged) return; _ignoreTextValueChanged = true; UnselectAll(); _ignoreTextValueChanged = false; UpdateText(); } private void _MultiSelectComboBoxItem_Selected(object sender, RoutedEventArgs e) { if (_ignoreTextValueChanged) return; _ignoreTextValueChanged = true; SelectAll(); _ignoreTextValueChanged = false; UpdateText(); } protected virtual void UpdateText() { if (_ignoreTextValueChanged) return; var newValue = string.Join(Delimiter, SelectedItems.Cast<object>().Select(x => GetItemDisplayValue(x))); if (string.IsNullOrWhiteSpace(Text) || !Text.Equals(newValue)) { _ignoreTextValueChanged = true; if (_multiSelectComboBoxItem != null) _multiSelectComboBoxItem.SetCurrentValue(IsSelectedProperty, SelectedItems.Count == Items.Count); SetCurrentValue(TextProperty, newValue); _ignoreTextValueChanged = false; } } protected object GetItemDisplayValue(object item) { if (string.IsNullOrWhiteSpace(DisplayMemberPath)) { var property = item.GetType().GetProperty("Content"); if (property != null) return property.GetValue(item, null); } var nameParts = DisplayMemberPath.Split('.'); if (nameParts.Length == 1) { var property = item.GetType().GetProperty(DisplayMemberPath); if (property != null) return property.GetValue(item, null); } return item; } } }
2) MultiSelectComboBoxItem.cs
代码如下:
using System.Windows.Controls; namespace WPFDevelopers.Controls { public class MultiSelectComboBoxItem : ListBoxItem { } }
3) MultiSelectComboBox.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.MergedDictionaries> <BooleanToVisibilityConverter x:Key="bool2VisibilityConverter" /> <Style x:Key="DefaultMultiSelectComboBoxItem" TargetType="{x:Type controls:MultiSelectComboBoxItem}"> <Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/> <Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/> <Setter Property="SnapsToDevicePixels" Value="True"/> <Setter Property="Background" Value="Transparent"/> <Setter Property="BorderBrush" Value="Transparent"/> <Setter Property="Foreground" Value="{DynamicResource RegularTextSolidColorBrush}"/> <Setter Property="BorderThickness" Value="0"/> <Setter Property="Height" Value="34" /> <Setter Property="Margin" Value="1,0" /> <Setter Property="Padding" Value="6,0"/> <Setter Property="Cursor" Value="Hand" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type controls:MultiSelectComboBoxItem}"> <Border x:Name="PART_Border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="true" Padding="{TemplateBinding Padding}"> <CheckBox Foreground="{TemplateBinding Foreground}" HorizontalAlignment="Stretch" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" MinHeight="{TemplateBinding MinHeight}" Padding="{TemplateBinding Padding}" IsChecked="{Binding IsSelected,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}"> <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" x:Name="PART_ContentPresenter" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" TextElement.Foreground="{TemplateBinding Foreground}"/> </CheckBox> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="{DynamicResource DefaultBackgroundSolidColorBrush}"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style TargetType="{x:Type controls:MultiSelectComboBox}"> <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" /> <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" /> <Setter Property="ScrollViewer.CanContentScroll" Value="True" /> <Setter Property="SelectionMode" Value="Multiple"/> <Setter Property="MinWidth" Value="120" /> <Setter Property="MinHeight" Value="{StaticResource MinHeight}" /> <Setter Property="Height" Value="{StaticResource MinHeight}" /> <Setter Property="ItemContainerStyle" Value="{StaticResource DefaultMultiSelectComboBoxItem}"/> <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:MultiSelectComboBox}"> <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" Text="{Binding Text,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}" Background="{TemplateBinding Background}" SelectionBrush="{DynamicResource WindowBorderBrushSolidColorBrush}" IsReadOnly="True" Style="{x:Null}" /> <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/> </Grid.RowDefinitions> <controls:MultiSelectComboBoxItem x:Name="PART_CheckBoxAll" Visibility="{TemplateBinding IsSelectAllActive,Converter={StaticResource bool2VisibilityConverter}}" Style="{TemplateBinding ItemContainerStyle}" Content="{TemplateBinding SelectAllContent}"/> <ScrollViewer x:Name="DropDownScrollViewer" Grid.Row="1" ScrollViewer.VerticalScrollBarVisibility="Auto"> <ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Contained" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/> </ScrollViewer> </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> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
4) MultiSelectComboBoxExample.xaml
代码如下:
<UserControl x:Class="WPFDevelopers.Samples.ExampleViews.MultiSelectComboBoxExample" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:WPFDevelopers.Samples.ExampleViews" xmlns:wpfdev="https://github.com/WPFDevelopersOrg/WPFDevelopers" xmlns:controls="clr-namespace:WPFDevelopers.Samples.Controls" xmlns:model="clr-namespace:WPFDevelopers.Sample.Models" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <UserControl.Resources> <model:HospitalList x:Key="myHospitalList"/> </UserControl.Resources> <controls:CodeViewer> <UniformGrid Columns="2"> <wpfdev:MultiSelectComboBox VerticalContentAlignment="Center" HorizontalAlignment="Center" Delimiter="^" Width="200"> <wpfdev:MultiSelectComboBoxItem>Option 1</wpfdev:MultiSelectComboBoxItem> <wpfdev:MultiSelectComboBoxItem>Option 2</wpfdev:MultiSelectComboBoxItem> <wpfdev:MultiSelectComboBoxItem>Option 3</wpfdev:MultiSelectComboBoxItem> <wpfdev:MultiSelectComboBoxItem>Option 4</wpfdev:MultiSelectComboBoxItem> <wpfdev:MultiSelectComboBoxItem>Option 5</wpfdev:MultiSelectComboBoxItem> </wpfdev:MultiSelectComboBox> <wpfdev:MultiSelectComboBox VerticalContentAlignment="Center" HorizontalAlignment="Center" IsSelectAllActive="True" ItemsSource="{Binding Source={StaticResource myHospitalList}}" DisplayMemberPath="DoctorName" SelectedValuePath="ID" Width="200"> </wpfdev:MultiSelectComboBox> </UniformGrid> <controls:CodeViewer.SourceCodes> <controls:SourceCodeModel CodeSource="/WPFDevelopers.SamplesCode;component/ExampleViews/MultiSelectComboBoxExample.xaml" CodeType="Xaml"/> <controls:SourceCodeModel CodeSource="/WPFDevelopers.SamplesCode;component/ExampleViews/MultiSelectComboBoxExample.xaml.cs" CodeType="CSharp"/> </controls:CodeViewer.SourceCodes> </controls:CodeViewer> </UserControl>
效果图
加载全部内容