网站首页 编程语言 正文
WPF 实现多选下拉控件
框架使用.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>
效果图

原文链接:https://mp.weixin.qq.com/s/uHloylkBzIbuxEiC5KEyBA
相关推荐
- 2022-10-24 visual studio 2022 编译出来的文件被删除并监视目录中的文件变更(示例详解)_C 语
- 2022-07-11 gstreamer的消息传递机制
- 2022-06-16 golang beego框架路由ORM增删改查完整案例_Golang
- 2023-01-13 Pytorch实现Fashion-mnist分类任务全过程_python
- 2022-05-23 Python学习之时间包使用教程详解_python
- 2022-11-06 Android实现圆形图片小工具_Android
- 2023-10-12 React实现Tab栏切换
- 2022-04-02 Redis快速实现分布式session的方法详解_Redis
- 最近更新
-
- window11 系统安装 yarn
- 超详细win安装深度学习环境2025年最新版(
- Linux 中运行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存储小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基础操作-- 运算符,流程控制 Flo
- 1. Int 和Integer 的区别,Jav
- spring @retryable不生效的一种
- Spring Security之认证信息的处理
- Spring Security之认证过滤器
- Spring Security概述快速入门
- Spring Security之配置体系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置权
- redisson分布式锁中waittime的设
- maven:解决release错误:Artif
- restTemplate使用总结
- Spring Security之安全异常处理
- MybatisPlus优雅实现加密?
- Spring ioc容器与Bean的生命周期。
- 【探索SpringCloud】服务发现-Nac
- Spring Security之基于HttpR
- Redis 底层数据结构-简单动态字符串(SD
- arthas操作spring被代理目标对象命令
- Spring中的单例模式应用详解
- 聊聊消息队列,发送消息的4种方式
- bootspring第三方资源配置管理
- GIT同步修改后的远程分支
