在Element UI中看到了级联框,恰好项目中需要使用,于是就仿照 element ui的样式做了一个
话不多说,先上效果


<ui:Cascader Name="cas" Width="200" IsMultipleChoose="True"/>
public MainWindow()
{
InitializeComponent();
cas.ItemsSource = TreeModel.ItemsSource();
}
public class TreeModel : SelectedPropertyChanged
{
public string Name { get; set; }
public string Code { get; set; }
public List<TreeModel> Children { get; set; }
public TreeModel() { }
public static List<TreeModel> ItemsSource()
{
return new List<TreeModel> {
new TreeModel {
Code = "1", Name = "指南", Children =
new List<TreeModel> {
new TreeModel { Code = "2", Name = "设计原则", Children =
new List<TreeModel> {
new TreeModel { Code = "3", Name = "一致" },
new TreeModel { Code = "3", Name = "反馈" },
new TreeModel { Code = "3", Name = "效率" },
new TreeModel { Code = "3", Name = "可控" },
}
},
new TreeModel { Code = "2", Name = "导航", Children =
new List<TreeModel> {
new TreeModel { Code = "3", Name = "侧向导航" },
new TreeModel { Code = "3", Name = "顶部导航" },
}
}
}
},
new TreeModel {
Code = "1", Name = "组件", Children =
new List<TreeModel> {
new TreeModel { Code = "2", Name = "Basic", Children =
new List<TreeModel> {
new TreeModel { Code = "3", Name = "Layout布局" },
new TreeModel { Code = "3", Name = "Color色彩" },
new TreeModel { Code = "3", Name = "字体" },
new TreeModel { Code = "3", Name = "图标" },
}
},
new TreeModel { Code = "2", Name = "Form", Children =
new List<TreeModel> {
new TreeModel { Code = "3", Name = "编辑表单" },
new TreeModel { Code = "3", Name = "提交表单" },
}
},
new TreeModel { Code = "2", Name = "Data", Children =
new List<TreeModel> {
new TreeModel { Code = "3", Name = "数据1" },
new TreeModel { Code = "3", Name = "数据2" },
}
}, new TreeModel { Code = "2", Name = "Notice", Children =
new List<TreeModel> {
new TreeModel { Code = "3", Name = "普通" },
new TreeModel { Code = "3", Name = "特殊" },
new TreeModel { Code = "3", Name = "顶级" },
new TreeModel { Code = "3", Name = "提示" },
}
},
}
},
};
}
}
public class SelectedPropertyChanged:BindableBase
{
private bool? isSelected = false;
public bool? IsSelected
{
get { return isSelected; }
set { isSelected = value;RaisePropertyChanged(); }
}
//private bool? isChecked;
//public bool? IsChecked
//{
// get { return isChecked; }
// set { isChecked = value; RaisePropertyChanged(); }
//}
}
public class Cascader:Control
{
private List<string> nameList = new List<string>();
private List<object> objList = new List<object>();
/// <summary>
/// 是否下拉展开
/// </summary>
public bool IsDropDown
{
get { return (bool)GetValue(IsDropDownProperty); }
set { SetValue(IsDropDownProperty, value); }
}
// Using a DependencyProperty as the backing store for IsDropDown. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsDropDownProperty =
DependencyProperty.Register("IsDropDown", typeof(bool), typeof(Cascader), new PropertyMetadata(default(bool)));
public string SelectedNamePath
{
get { return (string)GetValue(SelectedNamePathProperty); }
set { SetValue(SelectedNamePathProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedNamePath. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedNamePathProperty =
DependencyProperty.Register("SelectedNamePath", typeof(string), typeof(Cascader), new PropertyMetadata(default(string)));
public List<object> SelectedValues
{
get { return (List<object>)GetValue(SelectedValuesProperty); }
set { SetValue(SelectedValuesProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedValues. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedValuesProperty =
DependencyProperty.Register("SelectedValues", typeof(List<object>), typeof(Cascader), new PropertyMetadata(null));
public CornerRadius CornerRadius
{
get { return (CornerRadius)GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
// Using a DependencyProperty as the backing store for CornerRadius. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(Cascader), new PropertyMetadata(default(CornerRadius)));
/// <summary>
/// 内容资源
/// </summary>
public IEnumerable<object> ItemsSource
{
get { return (IEnumerable<object>)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
// Using a DependencyProperty as the backing store for ItemsSource. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable<object>), typeof(Cascader), new PropertyMetadata(null));
public object SelectedItem
{
get { return (object)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedItem. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(Cascader), new PropertyMetadata(default(object)));
public bool IsMultipleChoose
{
get { return (bool)GetValue(IsMultipleChooseProperty); }
set { SetValue(IsMultipleChooseProperty, value); }
}
// Using a DependencyProperty as the backing store for IsMultipleChoose. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsMultipleChooseProperty =
DependencyProperty.Register("IsMultipleChoose", typeof(bool), typeof(Cascader), new PropertyMetadata(default(bool), OnIsMultipleChooseChanged));
public bool IsLastDisplay
{
get { return (bool)GetValue(IsLastDisplayProperty); }
set { SetValue(IsLastDisplayProperty, value); }
}
// Using a DependencyProperty as the backing store for IsLastDisplay. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsLastDisplayProperty =
DependencyProperty.Register("IsLastDisplay", typeof(bool), typeof(Calendar), new PropertyMetadata(default(bool)));
private static void OnIsMultipleChooseChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Cascader cascader) {
if (cascader.cascaderItem != null) {
cascader.cascaderItem.IsMultipleChoose = (bool)e.NewValue;
}
}
}
private CascaderItem cascaderItem;
private TextBlock textBlock;
private Button showCleanButton;
private Border bd1;
public event EventHandler SelectedChanged;
public Cascader()
{
}
private void CascaderItem_SelectedChanged(object sender, EventArgs e)
{
nameList.Clear();
objList.Clear();
if (!IsMultipleChoose)
{
SelectedItem = cascaderItem.SelectedItem;
if (SelectedItem != null)
{
if (IsLastDisplay)
{
var nameProperty = SelectedItem.GetType().GetProperty("Name");
if (nameProperty != null)
{
object nameValue = nameProperty.GetValue(SelectedItem, null);
if (nameValue != null)
{
SelectedNamePath = nameValue.ToString();
}
}
}
else {
Recursion(cascaderItem.SelectedView);
nameList.Reverse();
objList.Reverse();
SelectedValues = objList;
SelectedNamePath = string.Join("/", nameList);
IsDropDown = false;
SelectedChanged?.Invoke(this, e);
}
}
}
else {
if (ItemsSource != null && ItemsSource is IEnumerable<object> list) {
//递归获取所有最后一级的选中状态
string str = "";
GetChildrenStates(list,str);
if (nameList.Count > 0)
{
SelectedNamePath = string.Join(",", nameList);
}
else {
SelectedNamePath = null;
}
}
}
textBlock.Text = SelectedNamePath?.ToString();
}
void GetChildrenStates(IEnumerable<object> array,string str)
{
foreach (var item in array)
{
if (!IsLastDisplay) {
var mianSelectProperty = item.GetType().GetProperty("IsSelected");
if (mianSelectProperty != null) {
var mainSelectValue = mianSelectProperty.GetValue(item, null);
if (mainSelectValue == null||(bool?)mainSelectValue == true) {
var mainNameProperty = item.GetType().GetProperty("Name");
if (mainNameProperty != null)
{
var mainNameValue = mainNameProperty.GetValue(item, null);
if (mainNameValue != null)
{
str += mainNameValue.ToString() + "/";
}
}
}
}
}
var childrenProperty = item.GetType().GetProperty("Children");
if (childrenProperty != null) {
object childrenValue = childrenProperty.GetValue(item, null);
if (childrenValue != null && childrenValue is IEnumerable<object> innerArray)
{
GetChildrenStates(innerArray, str);
}
else {
//最后一层
//判断最后一层是否已经勾选
var selectProperty = item.GetType().GetProperty("IsSelected");
if (selectProperty != null) {
var selectValue = selectProperty.GetValue(item, null);
if ((bool?)selectValue == true) {
//名字
var nameProperty = item.GetType().GetProperty("Name");
if (nameProperty != null) {
var nameVaule = nameProperty.GetValue(item, null);
if (!IsLastDisplay)
{
str += nameVaule?.ToString();
nameList.Add(str);
}
else {
nameList.Add(nameVaule?.ToString());
}
}
//对象
objList.Add(item);
}
}
}
}
}
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
cascaderItem = GetTemplateChild("cascaderItem") as CascaderItem;
textBlock = GetTemplateChild("textBlock") as TextBlock;
showCleanButton = GetTemplateChild("showCleanButton") as Button;
bd1 = GetTemplateChild("bd1") as Border;
if (cascaderItem != null)
{
cascaderItem.SelectedChanged += CascaderItem_SelectedChanged;
}
if (showCleanButton != null) {
showCleanButton.Click += ShowCleanButton_Click;
}
if (bd1 != null) {
bd1.MouseLeftButtonDown += OnMouseLeftButtonDown;
}
}
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
IsDropDown = !IsDropDown;
}
private void ShowCleanButton_Click(object sender, RoutedEventArgs e)
{
SelectedValues = null; SelectedNamePath = null;SelectedItem = null; textBlock.Text = null;IsDropDown = false;
}
/// <summary>
/// 递归计算所有的节点
/// </summary>
private void Recursion(CascaderInnerList innerObj) {
if (innerObj != null&&innerObj.SelectedItem!=null) {
var nameProperty = innerObj.SelectedItem.GetType().GetProperty("Name");
if (nameProperty != null)
{
object nameValue = nameProperty.GetValue(innerObj.SelectedItem, null);
if (nameValue != null)
{
nameList.Add(nameValue.ToString());
objList.Add(innerObj.SelectedItem);
}
//查看父级对象
if (innerObj.ParentSource != null) {
Recursion(innerObj.ParentSource);
}
}
}
}
}
<Style TargetType="local:Cascader">
<Setter Property="Height" Value="30"/>
<Setter Property="Width" Value="120"/>
<Setter Property="BorderBrush" Value="#dcdfe6"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="CornerRadius" Value="4"/>
<Setter Property="Foreground" Value="#606266"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:Cascader">
<ControlTemplate.Resources>
<converter:String2VisibilityConverter x:Key="String2VisibilityConverter"/>
<converter:String2VisibilityReConverter x:Key="String2VisibilityReConverter"/>
<converter:String2BooleanConverter x:Key="String2BooleanConverter"/>
<converter:CascaderPopupWidthConverter x:Key="CascaderPopupWidthConverter"/>
</ControlTemplate.Resources>
<Grid>
<Border x:Name="bd1" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" CornerRadius="{TemplateBinding CornerRadius}" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<Grid Margin="3 0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Path Grid.Column="2" x:Name="path1" Width="14" RenderTransformOrigin="0.5,0.5" Fill="{TemplateBinding BorderBrush}" Stretch="Uniform" Data="M512 745.8c-34.1 0-66.9-12.3-92.6-34.7L16.6 362.5c-20-17.3-22.2-47.7-4.9-67.7 17.3-20 47.7-22.2 67.7-4.9l403.1 348.8c16.8 14.7 42.2 14.7 59 0l0.2-0.2 402.9-348.7c20-17.3 50.4-15.2 67.7 4.9 17.3 20 15.2 50.4-4.9 67.7L604.6 711.1c-25.7 22.4-58.5 34.7-92.6 34.7z">
<Path.RenderTransform>
<TransformGroup>
<ScaleTransform />
<SkewTransform />
<RotateTransform x:Name="RotateTransform" Angle="0" />
<TranslateTransform />
</TransformGroup>
</Path.RenderTransform>
</Path>
<Button Grid.Column="1" Visibility="{Binding ElementName=textBlock,Path=Text,Converter={StaticResource String2VisibilityConverter}}" Name="showCleanButton" Width="18" Height="18">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border CornerRadius="7" Height="14" Name="bd_close" Width="14" Background="#dcdfe6">
<wpf:PackIcon Kind="Close" VerticalAlignment="Center" Width="12" HorizontalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="#606266" TargetName="bd_close"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#a9a9a9" TargetName="bd_close"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
<TextBlock FontSize="{TemplateBinding FontSize}" Text="请选择" Foreground="#c0c4d1" Margin="6 0 0 0" Visibility="{Binding ElementName=textBlock,Path=Text,Converter={StaticResource String2VisibilityReConverter}}" VerticalAlignment="Center"/>
<TextBlock TextTrimming="CharacterEllipsis" Foreground="{TemplateBinding Foreground}" ToolTipService.IsEnabled="{Binding ElementName=textBlock,Path=Text,Converter={StaticResource String2BooleanConverter}}" ToolTip="{Binding ElementName=textBlock,Path=Text}" x:Name="textBlock" FontSize="{TemplateBinding FontSize}" VerticalAlignment="Center"/>
<Popup x:Name="pop" MinWidth="{TemplateBinding Width,Converter={StaticResource CascaderPopupWidthConverter}}" StaysOpen="True" PopupAnimation="Slide" Placement="Bottom" PlacementTarget="{Binding ElementName=bd1}" IsOpen="{TemplateBinding IsDropDown}" VerticalOffset="-6" HorizontalOffset="-6" AllowsTransparency="True">
<local:CascaderItem ItemsSource="{TemplateBinding ItemsSource}" IsMultipleChoose="{TemplateBinding IsMultipleChoose}" x:Name="cascaderItem"/>
</Popup>
</Grid>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BorderBrush" Value="#c0c4cc"/>
</Trigger>
<Trigger Property="IsDropDown" Value="True">
<Setter Property="BorderBrush" Value="#409eff"/>
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="RotateTransform"
Storyboard.TargetProperty="Angle"
To="180"
Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="RotateTransform"
Storyboard.TargetProperty="Angle"
To="0"
Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
public class CascaderItem:Control
{
private StackPanel panel;
/// <summary>
/// 内容资源
/// </summary>
public IEnumerable<object> ItemsSource
{
get { return (IEnumerable<object>)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
// Using a DependencyProperty as the backing store for ItemsSource. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable<object>), typeof(CascaderItem), new PropertyMetadata(null));
public object SelectedItem
{
get { return (object)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedItem. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(CascaderItem), new PropertyMetadata(default(object)));
public CascaderInnerList SelectedView
{
get { return (CascaderInnerList)GetValue(SelectedViewProperty); }
set { SetValue(SelectedViewProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedView. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedViewProperty =
DependencyProperty.Register("SelectedView", typeof(CascaderInnerList), typeof(CascaderItem), new PropertyMetadata(null));
public bool IsMultipleChoose
{
get { return (bool)GetValue(IsMultipleChooseProperty); }
set { SetValue(IsMultipleChooseProperty, value); }
}
// Using a DependencyProperty as the backing store for IsMultipleChoose. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsMultipleChooseProperty =
DependencyProperty.Register("IsMultipleChoose", typeof(bool), typeof(CascaderItem), new PropertyMetadata(default(bool), OnIsMultipleChooseChanged));
private static void OnIsMultipleChooseChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is CascaderItem cascaderItem)
{
if (cascaderItem.panel != null && cascaderItem.Count > 0) {
foreach (var item in cascaderItem.panel.Children)
{
if (item is CascaderInnerList cascaderInner)
{
cascaderInner.IsMultipleChoose = (bool)e.NewValue;
}
else if (item is StackPanel stack) {
CascaderInnerList innerList = stack.Children[1] as CascaderInnerList;
innerList.IsMultipleChoose = (bool)e.NewValue;
}
}
}
}
}
private int Count { get { return panel.Children.Count; } }
public CascaderItem()
{
Loaded += CascaderItem_Loaded;
Unloaded += CascaderItem_Unloaded;
}
private void CascaderItem_Unloaded(object sender, RoutedEventArgs e)
{
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
panel = GetTemplateChild("sp_path") as StackPanel;
}
public event EventHandler SelectedChanged;
private void CascaderItem_Loaded(object sender, RoutedEventArgs e)
{
if (ItemsSource != null && panel != null&&panel.Children.Count==0)
{
CascaderInnerList cascaderInner = new CascaderInnerList();
cascaderInner.ItemsSource = ItemsSource;
cascaderInner.DeepIndex = 0;
cascaderInner.IsMultipleChoose = IsMultipleChoose;
cascaderInner.SelectionChanged += ListBox_SelectionChanged;
panel.Children.Add(cascaderInner);
}
}
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is CascaderInnerList inner) {
ListBox listBox = inner.innerListBox;
SelectedItem = listBox.SelectedItem;
//还要再次反射 看是不是最后一层
//通过反射获取数据
if (!IsMultipleChoose)
{
var innerGetType = listBox.SelectedItem.GetType().GetProperty("Children");
if (innerGetType != null)
{
object innerContent = innerGetType.GetValue(listBox.SelectedItem, null);
if (innerContent == null)
{
SelectedView = inner;
//是最后一层了 通过事件通知出去
SelectedChanged?.Invoke(this, EventArgs.Empty);
}
}
}
else {
SelectedView = inner;
//勾选便通知
SelectedChanged?.Invoke(this, EventArgs.Empty);
}
//通过层级关系 判断是否清空之前的数据
if (Count > inner.DeepIndex + 1)
{
panel.Children.RemoveRange(inner.DeepIndex + 1, panel.Children.Count - 1);
}
if (listBox.SelectedItem != null) {
var obj = listBox.SelectedItem;
//通过反射获取数据
var getType = obj.GetType().GetProperty("Children");
if (getType != null)
{
object content = getType.GetValue(obj, null);
if (content != null && content is IEnumerable<object> itemSource) { //判断是否存在子对象 也就是下级菜单
//存在开始拼接下级菜单
//添加分割线
//添加一个容器 好用于计算层数
StackPanel st = new StackPanel();
st.Orientation = Orientation.Horizontal;
//st.VerticalAlignment = VerticalAlignment.Center;
Border border = new Border();
border.Width = 1;
border.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#e4e7ed"));
st.Children.Add(border);
//添加子集菜单
CascaderInnerList cascaderInner = new CascaderInnerList();
cascaderInner.ItemsSource = itemSource;
cascaderInner.DeepIndex = inner.DeepIndex + 1;
cascaderInner.ParentSource = inner;
cascaderInner.IsMultipleChoose = IsMultipleChoose;
//cascaderInner.SetValue(Extensions.CascaderExtensions.MultipleChoiceProperty, false);
cascaderInner.SelectionChanged += ListBox_SelectionChanged;
st.Children.Add(cascaderInner);
panel.Children.Add(st);
}
}
}
}
}
}
<Style TargetType="local:CascaderItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:CascaderItem">
<Border Height="120" CornerRadius="4" Background="White" Margin="8" BorderThickness="1" BorderBrush="#dcdfe6" Effect="{StaticResource MaterialDesignElevationShadow4}">
<StackPanel Orientation="Horizontal" Name="sp_path">
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
public class CascaderItem:Control
{
private StackPanel panel;
/// <summary>
/// 内容资源
/// </summary>
public IEnumerable<object> ItemsSource
{
get { return (IEnumerable<object>)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
// Using a DependencyProperty as the backing store for ItemsSource. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable<object>), typeof(CascaderItem), new PropertyMetadata(null));
public object SelectedItem
{
get { return (object)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedItem. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(CascaderItem), new PropertyMetadata(default(object)));
public CascaderInnerList SelectedView
{
get { return (CascaderInnerList)GetValue(SelectedViewProperty); }
set { SetValue(SelectedViewProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedView. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedViewProperty =
DependencyProperty.Register("SelectedView", typeof(CascaderInnerList), typeof(CascaderItem), new PropertyMetadata(null));
public bool IsMultipleChoose
{
get { return (bool)GetValue(IsMultipleChooseProperty); }
set { SetValue(IsMultipleChooseProperty, value); }
}
// Using a DependencyProperty as the backing store for IsMultipleChoose. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsMultipleChooseProperty =
DependencyProperty.Register("IsMultipleChoose", typeof(bool), typeof(CascaderItem), new PropertyMetadata(default(bool), OnIsMultipleChooseChanged));
private static void OnIsMultipleChooseChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is CascaderItem cascaderItem)
{
if (cascaderItem.panel != null && cascaderItem.Count > 0) {
foreach (var item in cascaderItem.panel.Children)
{
if (item is CascaderInnerList cascaderInner)
{
cascaderInner.IsMultipleChoose = (bool)e.NewValue;
}
else if (item is StackPanel stack) {
CascaderInnerList innerList = stack.Children[1] as CascaderInnerList;
innerList.IsMultipleChoose = (bool)e.NewValue;
}
}
}
}
}
private int Count { get { return panel.Children.Count; } }
public CascaderItem()
{
Loaded += CascaderItem_Loaded;
Unloaded += CascaderItem_Unloaded;
}
private void CascaderItem_Unloaded(object sender, RoutedEventArgs e)
{
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
panel = GetTemplateChild("sp_path") as StackPanel;
}
public event EventHandler SelectedChanged;
private void CascaderItem_Loaded(object sender, RoutedEventArgs e)
{
if (ItemsSource != null && panel != null&&panel.Children.Count==0)
{
CascaderInnerList cascaderInner = new CascaderInnerList();
cascaderInner.ItemsSource = ItemsSource;
cascaderInner.DeepIndex = 0;
cascaderInner.IsMultipleChoose = IsMultipleChoose;
cascaderInner.SelectionChanged += ListBox_SelectionChanged;
panel.Children.Add(cascaderInner);
}
}
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is CascaderInnerList inner) {
ListBox listBox = inner.innerListBox;
SelectedItem = listBox.SelectedItem;
//还要再次反射 看是不是最后一层
//通过反射获取数据
if (!IsMultipleChoose)
{
var innerGetType = listBox.SelectedItem.GetType().GetProperty("Children");
if (innerGetType != null)
{
object innerContent = innerGetType.GetValue(listBox.SelectedItem, null);
if (innerContent == null)
{
SelectedView = inner;
//是最后一层了 通过事件通知出去
SelectedChanged?.Invoke(this, EventArgs.Empty);
}
}
}
else {
SelectedView = inner;
//勾选便通知
SelectedChanged?.Invoke(this, EventArgs.Empty);
}
//通过层级关系 判断是否清空之前的数据
if (Count > inner.DeepIndex + 1)
{
panel.Children.RemoveRange(inner.DeepIndex + 1, panel.Children.Count - 1);
}
if (listBox.SelectedItem != null) {
var obj = listBox.SelectedItem;
//通过反射获取数据
var getType = obj.GetType().GetProperty("Children");
if (getType != null)
{
object content = getType.GetValue(obj, null);
if (content != null && content is IEnumerable<object> itemSource) { //判断是否存在子对象 也就是下级菜单
//存在开始拼接下级菜单
//添加分割线
//添加一个容器 好用于计算层数
StackPanel st = new StackPanel();
st.Orientation = Orientation.Horizontal;
//st.VerticalAlignment = VerticalAlignment.Center;
Border border = new Border();
border.Width = 1;
border.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#e4e7ed"));
st.Children.Add(border);
//添加子集菜单
CascaderInnerList cascaderInner = new CascaderInnerList();
cascaderInner.ItemsSource = itemSource;
cascaderInner.DeepIndex = inner.DeepIndex + 1;
cascaderInner.ParentSource = inner;
cascaderInner.IsMultipleChoose = IsMultipleChoose;
//cascaderInner.SetValue(Extensions.CascaderExtensions.MultipleChoiceProperty, false);
cascaderInner.SelectionChanged += ListBox_SelectionChanged;
st.Children.Add(cascaderInner);
panel.Children.Add(st);
}
}
}
}
}
}
<Style TargetType="local:CascaderInnerList">
<Setter Property="IsMultipleChoose" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:CascaderInnerList">
<ListBox ItemsSource="{TemplateBinding ItemsSource}" Name="listBox">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Margin" Value="3 1"/>
<Setter Property="Padding" Value="6 8"/>
<Setter Property="MinWidth" Value="60"/>
<Setter Property="Focusable" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<ControlTemplate.Resources>
<converter:Object2VisibilityConverter x:Key="Object2VisibilityConverter"/>
<converter:Boolean2VisibilityConverter x:Key="Boolean2VisibilityConverter"/>
</ControlTemplate.Resources>
<Border x:Name="bd1" Padding="{TemplateBinding Padding}">
<Grid x:Name="gd1" Background="Transparent" VerticalAlignment="Center" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*" MinWidth="20"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<CheckBox Margin="0 0 4 0" Focusable="False" Name="checkbox" IsChecked="{Binding IsSelected,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}}" Command="{x:Static local:CascaderInnerList.ClickCommand}" Visibility="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:CascaderInnerList}},Path=IsMultipleChoose,Converter={StaticResource Boolean2VisibilityConverter}}"/>
<TextBlock Text="{Binding Name}" Tag="1" Name="tb1" TextAlignment="Center" VerticalAlignment="Center" FontSize="14" Grid.Column="1"/>
<Path Width="11" Height="11" Visibility="{Binding Children,Converter={StaticResource Object2VisibilityConverter}}" Grid.Column="3" Name="path" Stretch="Uniform" Fill="Black" Data="M767.055482 512.122299a51.11001 51.11001 0 0 1-12.412431 36.50715L329.699826 1010.51791a41.618151 41.618151 0 0 1-61.332012 0 51.11001 51.11001 0 0 1 0-66.077942l398.658077-432.536712L268.367814 79.29353a51.11001 51.11001 0 0 1 0-65.71287 41.618151 41.618151 0 0 1 61.332012 0l424.943225 461.88846a51.11001 51.11001 0 0 1 12.412431 36.50715v0.146029z"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#f5f7fa" TargetName="bd1"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="#40a9fe" TargetName="tb1"/>
<Setter Property="Fill" Value="#40a9fe" TargetName="path"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
public class CascaderInnerList:Control
{
/// <summary>
/// 内容资源
/// </summary>
public IEnumerable<object> ItemsSource
{
get { return (IEnumerable<object>)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
// Using a DependencyProperty as the backing store for ItemsSource. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable<object>), typeof(CascaderInnerList), new PropertyMetadata(null));
public object SelectedItem
{
get { return (object)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedItem. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(CascaderInnerList), new PropertyMetadata(default(object)));
public bool IsMultipleChoose
{
get { return (bool)GetValue(IsMultipleChooseProperty); }
set { SetValue(IsMultipleChooseProperty, value); }
}
// Using a DependencyProperty as the backing store for IsMultipleChoose. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsMultipleChooseProperty =
DependencyProperty.Register("IsMultipleChoose", typeof(bool), typeof(CascaderInnerList), new PropertyMetadata(default(bool)));
public static ICommand ClickCommand { get; private set; }
public ListBox innerListBox;
public event SelectionChangedEventHandler SelectionChanged;
/// <summary>
/// 深度
/// </summary>
public int DeepIndex { get; set; }
public CascaderInnerList ParentSource { get; set; }
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
innerListBox = GetTemplateChild("listBox") as ListBox;
innerListBox.SelectionChanged += (e, s) => {
SelectedItem = innerListBox.SelectedItem;
SelectionChanged?.Invoke(this, s);
};
}
public CascaderInnerList()
{
Loaded += CascaderInnerList_Loaded;
ClickCommand = new DelegateCommand<object>(ClickCommandExecute);
}
private void CascaderInnerList_Loaded(object sender, RoutedEventArgs e)
{
}
private void ClickCommandExecute(object obj)
{
if (obj != null && obj is CheckBox box) {
var checkState = box.IsChecked;
var data = box.DataContext;
innerListBox.SelectedItem = data;
//设置子对象状态
SetChildrenState(checkState,data);
//设置父对象状态
SetParentState(checkState,this);
SelectionChanged?.Invoke(this, null);
}
}
private void SetParentState(bool? checkState, CascaderInnerList innerObj)
{
//获取父对象状态
if (innerObj.ItemsSource != null && innerObj.ItemsSource is IEnumerable<object> array)
{
List<bool?> selectValues = new List<bool?>();
foreach (object item in array)
{
var selectedProperty = item.GetType().GetProperty("IsSelected");
if (selectedProperty != null)
{
var selectValue = selectedProperty.GetValue(item, null) as bool?;
selectValues.Add(selectValue);
}
}
if (selectValues.Count(x => x == true) == array.Count())
{
checkState = true;
}
else if (selectValues.Count(x => x == false) == array.Count())
{
checkState = false;
}
else {
checkState = null;
}
}
if (innerObj.ParentSource != null && innerObj.ParentSource.SelectedItem != null)
{
var selectedProperty = innerObj.ParentSource.SelectedItem.GetType().GetProperty("IsSelected");
if (selectedProperty != null)
{
selectedProperty.SetValue(innerObj.ParentSource.SelectedItem, checkState);
//查看父级对象
if (innerObj.ParentSource != null)
{
SetParentState(checkState, innerObj.ParentSource);
}
}
}
}
void SetChildrenState(bool? state,object data) {
var childrenProperty = data.GetType().GetProperty("Children");
if (childrenProperty != null)
{
object childrenValue = childrenProperty.GetValue(data, null);
if (childrenValue != null&&childrenValue is IEnumerable<object> array)
{
foreach ( object item in array)
{
var selectedProperty = item.GetType().GetProperty("IsSelected");
if (selectedProperty != null) {
selectedProperty.SetValue(item,state);
SetChildrenState(state, item);
}
}
}
}
}
}
免责声明:本文系网络转载或改编,未找到原创作者,版权归原作者所有。如涉及版权,请联系删