c# wpf 自定义可伸缩瓦片状多选控件
一、效果图
二、代码
1、ListBox做架构主界面
1.1 xaml
<UserControl x:Class="YZ.HIS.UserControls.TelescopicMultiSelector.TelescopicMultiSelector"
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:YZ.HIS.UserControls.TelescopicMultiSelector"
mc:Ignorable="d" >
<StackPanel Margin="4" x:Name="outPanel">
<DockPanel>
<Border Margin="4" Padding="4" x:Name="tip">
<TextBlock x:Name="textBlock">(多选)</TextBlock>
</Border>
<ListBox ItemsSource="{Binding DataList1}" x:Name="ListBoxFirst">
<ListBox.Template>
<ControlTemplate>
<StackPanel IsItemsHost="True" Orientation="Horizontal"/>
</ControlTemplate>
</ListBox.Template>
</ListBox>
<Border Margin="4" Padding="4" HorizontalAlignment="Right">
<Button x:Name="button" Style="{DynamicResource btnText}" Click="Button_Click" Cursor="Hand">
<Button.Template>
<ControlTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="btnTB" Text="{Binding BtnText}"></TextBlock>
<Image Source="{Binding BtnImg}" Width="16" Height="16"></Image>
</StackPanel>
</ControlTemplate>
</Button.Template>
</Button>
</Border>
</DockPanel>
<ListBox ItemsSource="{Binding DataList2}" Visibility="Collapsed" x:Name="ListBoxSec">
<ListBox.Template>
<ControlTemplate>
<WrapPanel IsItemsHost="True" Orientation="Horizontal"></WrapPanel>
</ControlTemplate>
</ListBox.Template>
</ListBox>
</StackPanel>
</UserControl>
2.2 cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using YZ.HIS.Common;
namespace YZ.HIS.UserControls.TelescopicMultiSelector
{
/// <summary>
/// TelescopicMultiSelector.xaml 的交互逻辑
/// </summary>
public partial class TelescopicMultiSelector : UserControl,INotifyPropertyChanged
{
public EventHandler SelectComplete;
//传入id,name的source和selectedList
private List<KeyValuePair<long, string>> IdNameSourceList { get; set; }
private List<KeyValuePair<long, string>> IdNameSelectedList { get; set; }
//只传入name的source和selectedList
private List<String> NameSourceList { get; set; }
private List<String> NameSelectedList { get; set; }
private bool WithId { get; set; } = false;
private List<TMSelectorItem> _DataList1 = new List<TMSelectorItem>();
public List<TMSelectorItem> DataList1
{
get
{
return _DataList1;
}
set
{
_DataList1 = value;
PropertyChange("DataList1");
}
}
private List<TMSelectorItem> _DataList2 = new List<TMSelectorItem>();
public List<TMSelectorItem> DataList2
{
get
{
return _DataList2;
}
set
{
_DataList2 = value;
PropertyChange("DataList2");
}
}
private List<string> SelectedList = null;
private Dictionary<string, KeyValuePair<long, string>> SelectedDic = null;
private Dictionary<string, KeyValuePair<long, string>> SourceDic = null;
private double Size { get; set; } = 4D;
private double FirstRowSize { get; set; } = 0D;
private bool OpenStatus { get; set; } = false;
public string BtnText
{
get
{
if (OpenStatus)
return "收起";
return "展开";
}
}
public string BtnImg
{
get
{
if (OpenStatus)
return "/YZ.His;component/Images/OnlineHospital/arrow_up.png";
return "/YZ.His;component/Images/OnlineHospital/arrow_down.png";
}
}
public TelescopicMultiSelector(double Width, List<KeyValuePair<long, string>> IdNameSourceList, List<KeyValuePair<long, string>> IdNameSelectList = null,double FontSize = 12)
{
this.IdNameSourceList = IdNameSourceList;
this.IdNameSelectedList = IdNameSelectedList;
this.Width = Width;
this.FontSize = FontSize;
this.WithId = true;
this.DataContext = this;
InitializeComponent();
InitStruct();
InitListBox();
SelectComplete += sc;
}
private void sc(object sender,EventArgs e)
{
}
public TelescopicMultiSelector(double Width, List<string> NameSourceList, List<string> NameSelectList = null, double FontSize = 12)
{
this.NameSourceList = NameSourceList;
this.NameSelectedList = NameSelectedList;
this.Width = Width;
this.FontSize = FontSize;
this.WithId = false;
this.DataContext = this;
InitializeComponent();
InitStruct();
InitListBox();
SelectComplete += sc;
}
public event PropertyChangedEventHandler PropertyChanged;
private void PropertyChange(string val)
{
if (string.IsNullOrEmpty(val))
return;
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(val));
}
private void InitStruct()
{
// 设置listbox的SelectionMode.Multiple,ListBox可以多选***
this.ListBoxFirst.SelectionMode = SelectionMode.Multiple;
this.ListBoxSec.SelectionMode = SelectionMode.Multiple;
if (this.FontSize != 12)
{
Size = this.FontSize / 3;
// 初始化多选和展开按钮的位置
// 设置margin和padding
this.outPanel.Margin = new Thickness(Size);
this.tip.Margin = new Thickness(Size);
this.tip.Padding = new Thickness(Size);
this.button.Margin = new Thickness(Size);
this.button.Padding = new Thickness(Size);
}
FirstRowSize = MeasureString(this.textBlock).Width + MeasureString(this.textBlock, BtnText).Width+Size*10 +16;//16是image的宽度数据
}
// 初始化加载ListBox及其中的items
private void InitListBox()
{
//带id的传入处理
if (WithId)
{
if (IdNameSourceList == null || IdNameSourceList.Count == 0)
return;
if (IdNameSelectedList != null && IdNameSelectedList.Count > 0)
{
if (SelectedDic == null)
SelectedDic = new Dictionary<string, KeyValuePair<long, string>>();
foreach (KeyValuePair<long, string> pair in IdNameSelectedList)
{
if (pair.Key==0L)
continue;
SelectedDic.Add(pair.Key + "", pair);
}
}
if (SourceDic == null)
SourceDic = new Dictionary<string, KeyValuePair<long, string>>();
foreach (KeyValuePair<long, string> pair in IdNameSourceList)
{
long id = pair.Key;
string name = pair.Value;
if (id == 0L || string.IsNullOrEmpty(name))
continue;
string key = id + "";
SourceDic.Add(key,pair);
TMSelectorItem item = new TMSelectorItem()
{
FontSize = this.FontSize,
SpaceSize = Size,
TextContent = name,
Uid = key
};
item.SelectEvent += SelectAndRemove;
if (SelectedDic != null && SelectedDic.ContainsKey(key))
item.IsSelected = true;
DataListDevide(item);
}
}
//不带id的传入处理
else
{
if (NameSourceList == null || NameSourceList.Count == 0)
return;
if (NameSelectedList != null && NameSelectedList.Count > 0)
{
if (SelectedList == null)
SelectedList = new List<string>();
foreach (string str in NameSelectedList)
SelectedList.Add(str);
}
foreach(string str in NameSourceList)
{
if (string.IsNullOrEmpty(str))
continue;
TMSelectorItem item = new TMSelectorItem()
{
FontSize = this.FontSize,
SpaceSize = Size,
TextContent = str,
Uid = str
};
item.SelectEvent += SelectAndRemove;
if (SelectedList != null && SelectedList.Contains(str))
item.IsSelected = true;
DataListDevide(item);
}
}
}
//选择或取消每一项
private void SelectAndRemove(object sender,EventArgs e)
{
var sItem = sender as TMSelectorItem;
if(WithId)
{
SelectedDicHandle(sItem.IsSelected, sItem.Uid);
SelectComplete.Invoke(SelectedDic.Values, e);
}
else
{
SelectedListHandle(sItem.IsSelected, sItem.Uid);
SelectComplete.Invoke(SelectedList, e);
}
}
private void SelectedDicHandle(bool flag,string key)
{
//true为新增操作,false为移除操作
if (flag)
{
if (SelectedDic == null)
SelectedDic = new Dictionary<string, KeyValuePair<long, string>>();
if (SelectedDic.ContainsKey(key))
return;
SelectedDic.Add(key, SourceDic[key]);
}
else
{
SelectedDic.Remove(key);
}
}
private void SelectedListHandle(bool flag,string content)
{
if (flag)
{
if (SelectedList == null)
SelectedList = new List<string>();
if (SelectedList.Contains(content))
return;
SelectedList.Add(content);
}
else
{
SelectedList.Remove(content);
}
}
//将source区分处理,分为两部分分别进行第一行和下面的展开项中显示
private void DataListDevide(TMSelectorItem item)
{
if(FirstRowSize < this.Width)
FirstRowSize += MeasureString(this.textBlock,item.TextContent).Width+Size*4;
if (FirstRowSize > this.Width)
{
DataList2.Add(item);
return;
}
DataList1.Add(item);
}
// 点击进行展开或收起的处理
private void Button_Click(object sender, RoutedEventArgs e)
{
OpenStatus = !OpenStatus;
//按钮变化显示
ButtonDisPlay();
if (OpenStatus)
{
this.ListBoxSec.Visibility = Visibility.Visible;
}
else
{
this.ListBoxSec.Visibility = Visibility.Collapsed;
}
}
private void ButtonDisPlay()
{
PropertyChange("BtnText");
PropertyChange("BtnImg");
}
//计算TextBlock的大小
private Size MeasureString(TextBlock tb,string content=null)
{
if (content == null)
content = tb.Text.ToString();
var formattedText = new FormattedText(
content,
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface(tb.FontFamily, tb.FontStyle, tb.FontWeight, tb.FontStretch),
tb.FontSize,
Brushes.Black,
new NumberSubstitution(),
TextFormattingMode.Display);
return new Size(formattedText.Width, formattedText.Height);
}
}
}
2、ListBoxItem每个可选择项
2.1 xaml
注:listBoxItem中不能通过触发器对其中的元素的foreground或background等进行设置。可以将需要设置的元素Property项的value与listBoxItem自身的相关联 如下写法:
BorderBrush="{Binding Foreground,RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
再使用触发器设置listBoxItem就好,从12行<Style.Triggers>开始。
<ListBoxItem x:Class="YZ.HIS.UserControls.TelescopicMultiSelector.TMSelectorItem"
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:YZ.HIS.UserControls.TelescopicMultiSelector"
mc:Ignorable="d" Style="{DynamicResource ThisItemStyle}">
<ListBoxItem.Resources>
<Style TargetType="ListBoxItem" x:Key="ThisItemStyle">
<Setter Property="Foreground" Value="#b2b2b2"></Setter>
<Setter Property="Cursor" Value="Hand"></Setter>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected}" Value="True"></Condition>
</MultiDataTrigger.Conditions>
<Setter Property="Foreground" Value="#076ff5"></Setter>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected}" Value="False"></Condition>
</MultiDataTrigger.Conditions>
<Setter Property="Foreground" Value="#b2b2b2"></Setter>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</ListBoxItem.Resources>
<ListBoxItem.Template>
<ControlTemplate>
<RadioButton Click="RadioButton_Click">
<RadioButton.Template>
<ControlTemplate>
<Border CornerRadius="5" Background="#fff" BorderThickness="1" Padding="{Binding SpaceSize}" Margin="{Binding SpaceSize}" BorderBrush="{Binding Foreground,RelativeSource={RelativeSource AncestorType=ListBoxItem}}">
<TextBlock Text="{Binding TextContent}" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="{Binding Foreground,RelativeSource={RelativeSource AncestorType=ListBoxItem}}"></TextBlock>
</Border>
</ControlTemplate>
</RadioButton.Template>
</RadioButton>
</ControlTemplate>
</ListBoxItem.Template>
</ListBoxItem>
2.2 cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace YZ.HIS.UserControls.TelescopicMultiSelector
{
/// <summary>
/// TMSelectorItem.xaml 的交互逻辑
/// </summary>
public partial class TMSelectorItem : ListBoxItem
{
public double SpaceSize { get; set; } = 4;
public string TextContent { get; set; } = "";
public EventHandler SelectEvent;
public TMSelectorItem()
{
InitializeComponent();
this.DataContext = this;
}
private void RadioButton_Click(object sender, RoutedEventArgs e)
{
//按钮的选择与listBoxItem 的选择关联
var btn = sender as RadioButton;
if (this.IsSelected)
btn.IsChecked = false;
if (btn.IsChecked == true)
{
this.IsSelected = true;
}
else
{
this.IsSelected = false;
}
// 选择后触发事件
SelectEvent.Invoke(this, new EventArgs());
}
}
}