WPF利用CommunityToolkit.Mvvm实现级联选择器

 更新时间:2023年12月25日 10:16:59   作者:WPF开发者  
这篇文章主要介绍了WPF如何利用CommunityToolkit.Mvvm实现级联选择器,文中的示例代码讲解详细,对我们的学习或工作有一定帮助,需要的小伙伴可以参考一下

WPF 使用 CommunityToolkit.Mvvm 实现级联选择器

框架使用.NET5

Visual Studio 2022;

示例代码

1)CascadePicker.cs 代码如下:

  • Text 获取或设置级联选择器的文本值。
  • IsDropDownOpen 级联选择器的下拉菜单是否打开。
  • MaxDropDownHeight 级联选择器下拉菜单的最大高度。
  • OnApplyTemplate 重写了基类的模板应用方法。
  • MenuItem_Click 菜单项点击事件,根据用户选择的菜单项更新级联选择器的文本值,并关闭下拉菜单。
  • GetHierarchicalPath 菜单项的层次结构获取完整的路径字符串。
  • GetParentHierarchy 菜单项的父级层次结构。
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;

namespace WpfCascadePicker.Controls
{
    public class CascadePicker : MenuBase
    {
        private static readonly Type _typeofSelf = typeof(CascadePicker);
        static CascadePicker()
        {
            DefaultStyleKeyProperty.OverrideMetadata(_typeofSelf, new FrameworkPropertyMetadata(_typeofSelf));
        }

        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }

        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register("Text", typeof(string), typeof(CascadePicker), new PropertyMetadata(string.Empty));



        public static readonly DependencyProperty IsDropDownOpenProperty = DependencyProperty.Register(
            "IsDropDownOpen", typeof(bool), typeof(CascadePicker), new PropertyMetadata(false));

        public bool IsDropDownOpen
        {
            get { return (bool)GetValue(IsDropDownOpenProperty); }
            set { SetValue(IsDropDownOpenProperty, value); }
        }

        public static readonly DependencyProperty MaxDropDownHeightProperty = DependencyProperty.Register(
            "MaxDropDownHeight", typeof(double), typeof(CascadePicker), new PropertyMetadata(SystemParameters.PrimaryScreenHeight / 3.0));
        public double MaxDropDownHeight
        {
            get
            {
                return (double)GetValue(MaxDropDownHeightProperty);
            }
            set
            {
                SetValue(MaxDropDownHeightProperty, value);
            }
        }
        
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
        }
        public CascadePicker()
        {
            AddHandler(MenuItem.ClickEvent, new RoutedEventHandler(MenuItem_Click));
        }
        private void MenuItem_Click(object sender, RoutedEventArgs e)
        {
            var clickedMenuItem = e.OriginalSource as MenuItem;
            if (clickedMenuItem != null)
            {
                List<string> hierarchy = GetParentHierarchy(clickedMenuItem);
                Text = GetHierarchicalPath(hierarchy);
            }
            IsDropDownOpen = false;
        }
        private string GetHierarchicalPath(List<string> hierarchy)
        {
            hierarchy.Reverse();
            return string.Join(" / ", hierarchy);
        }
        private List<string> GetParentHierarchy(MenuItem item)
        {
            var hierarchy = new List<string>();

            var headerObject = item.Header;
            var propertyInfo = headerObject.GetType().GetProperty(DisplayMemberPath);
            if (propertyInfo != null)
            {
                var displayValue = propertyInfo.GetValue(headerObject, null) as string;
                if (!string.IsNullOrEmpty(displayValue))
                    hierarchy.Add(displayValue);
            }
            var parent = VisualTreeHelper.GetParent(item);
            if (parent != null)
            {
                var stackPanel = parent as StackPanel;
                if (stackPanel != null)
                {
                    var menuItemParent = stackPanel.TemplatedParent as MenuItem;
                    if (menuItemParent != null)
                    {
                        var parentHierarchy = GetParentHierarchy(menuItemParent);
                        hierarchy.AddRange(parentHierarchy);
                    }
                }
            }
            return hierarchy;
        }
    }
}

2)CascadePicker.xaml 代码如下:

  • 子菜单项的样式使用了WD.DefaultMenuItem样式,并绑定了ItemsSource属性。
  • 定义了控件的外观和行为,包括边框、文本框、下拉箭头、下拉弹出窗口等元素。
  • 触发器定义了控件的交互行为,例如鼠标悬停、下拉弹出窗口的打开和关闭等。
<Application
    x:Class="WpfCascade.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:controls="clr-namespace:WpfCascadePicker.Controls"
    xmlns:local="clr-namespace:WpfCascade"
    xmlns:wd="https://github.com/WPFDevelopersOrg/WPFDevelopers"
    StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="pack://application:,,,/WPFDevelopers;component/Themes/Light.Blue.xaml" />
                <!--  需要注意 wd:Resources 必须在配色主题后,Theme="Dark" 为黑色皮肤  -->
                <wd:Resources Theme="Light" />
                <ResourceDictionary Source="pack://application:,,,/WPFDevelopers;component/Themes/Theme.xaml" />
            </ResourceDictionary.MergedDictionaries>
            <Style BasedOn="{StaticResource WD.ControlBasicStyle}" TargetType="{x:Type controls:CascadePicker}">
                <Setter Property="HorizontalContentAlignment" Value="Left" />
                <Setter Property="VerticalContentAlignment" Value="Center" />
                <Setter Property="BorderBrush" Value="{DynamicResource WD.BaseSolidColorBrush}" />
                <Setter Property="BorderThickness" Value="1" />
                <Setter Property="Background" Value="{DynamicResource WD.BackgroundSolidColorBrush}" />
                <Setter Property="Padding" Value="{StaticResource WD.DefaultPadding}" />
                <Setter Property="ItemContainerStyle">
                    <Setter.Value>
                        <Style BasedOn="{StaticResource WD.DefaultMenuItem}" TargetType="MenuItem">
                            <Setter Property="ItemsSource" Value="{Binding ItemsSource}" />
                            <Setter Property="ItemContainerStyle">
                                <Setter.Value>
                                    <Style BasedOn="{StaticResource WD.DefaultMenuItem}" TargetType="MenuItem">
                                        <Setter Property="ItemsSource" Value="{Binding ItemsSource}" />
                                    </Style>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </Setter.Value>
                </Setter>
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type controls:CascadePicker}">
                            <ControlTemplate.Resources>
                                <Storyboard x:Key="OpenStoryboard">
                                    <DoubleAnimation
                                        EasingFunction="{StaticResource WD.ExponentialEaseOut}"
                                        Storyboard.TargetName="PART_DropDown"
                                        Storyboard.TargetProperty="(Grid.RenderTransform).(ScaleTransform.ScaleY)"
                                        To="1"
                                        Duration="00:00:.2" />
                                </Storyboard>
                                <Storyboard x:Key="CloseStoryboard">
                                    <DoubleAnimation
                                        EasingFunction="{StaticResource WD.ExponentialEaseOut}"
                                        Storyboard.TargetName="PART_DropDown"
                                        Storyboard.TargetProperty="(Grid.RenderTransform).(ScaleTransform.ScaleY)"
                                        To="0"
                                        Duration="00:00:.2" />
                                </Storyboard>
                            </ControlTemplate.Resources>
                            <wd:SmallPanel SnapsToDevicePixels="True">
                                <Border
                                    Name="PART_Border"
                                    Background="{TemplateBinding Background}"
                                    BorderBrush="{TemplateBinding BorderBrush}"
                                    BorderThickness="{TemplateBinding BorderThickness}"
                                    CornerRadius="{Binding Path=(wd:ElementHelper.CornerRadius), RelativeSource={RelativeSource TemplatedParent}}"
                                    SnapsToDevicePixels="True" />
                                <TextBox
                                    Name="PART_EditableTextBox"
                                    Margin="{TemplateBinding Padding}"
                                    HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                    VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                    Background="{TemplateBinding Background}"
                                    Focusable="True"
                                    Foreground="{DynamicResource WD.PrimaryTextSolidColorBrush}"
                                    IsReadOnly="True"
                                    SelectionBrush="{DynamicResource WD.WindowBorderBrushSolidColorBrush}"
                                    Style="{x:Null}"
                                    Template="{StaticResource WD.ComboBoxTextBox}"
                                    Text="{Binding Text, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
                                <TextBlock
                                    Name="PART_Watermark"
                                    Margin="{TemplateBinding Padding}"
                                    Padding="1,0"
                                    HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                    VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                    Background="Transparent"
                                    Foreground="{DynamicResource WD.RegularTextSolidColorBrush}"
                                    IsHitTestVisible="False"
                                    Text="{Binding Path=(wd:ElementHelper.Watermark), RelativeSource={RelativeSource TemplatedParent}}"
                                    TextBlock.FontSize="{StaticResource WD.NormalFontSize}"
                                    TextTrimming="CharacterEllipsis"
                                    Visibility="Collapsed" />
                                <ToggleButton
                                    Name="PART_ToggleButton"
                                    Background="{TemplateBinding Background}"
                                    ClickMode="Release"
                                    Focusable="False"
                                    IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
                                    Style="{x:Null}"
                                    Template="{StaticResource WD.ComboBoxToggleButton}" />
                                <Popup
                                    Name="PART_Popup"
                                    AllowsTransparency="True"
                                    IsOpen="{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
                                    Placement="Bottom"
                                    PlacementTarget="{Binding ElementName=PART_ToggleButton}"
                                    StaysOpen="False">
                                    <wd:SmallPanel
                                        Name="PART_DropDown"
                                        MinWidth="{TemplateBinding FrameworkElement.ActualWidth}"
                                        MaxHeight="{TemplateBinding MaxDropDownHeight}"
                                        Margin="24,2,24,24"
                                        RenderTransformOrigin=".5,0"
                                        SnapsToDevicePixels="True">
                                        <wd:SmallPanel.RenderTransform>
                                            <ScaleTransform ScaleY="0" />
                                        </wd:SmallPanel.RenderTransform>
                                        <Border
                                            Name="PART_DropDownBorder"
                                            Background="{TemplateBinding Background}"
                                            BorderBrush="{TemplateBinding BorderBrush}"
                                            BorderThickness="{TemplateBinding BorderThickness}"
                                            CornerRadius="{Binding Path=(wd:ElementHelper.CornerRadius), RelativeSource={RelativeSource TemplatedParent}}"
                                            Effect="{StaticResource WD.PopupShadowDepth}"
                                            SnapsToDevicePixels="True"
                                            UseLayoutRounding="True">
                                            <ItemsPresenter />
                                        </Border>
                                    </wd:SmallPanel>
                                </Popup>
                            </wd: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="UIElement.IsMouseOver" Value="True">
                                    <Setter TargetName="PART_Border" Property="BorderBrush" Value="{DynamicResource WD.PrimaryNormalSolidColorBrush}" />
                                </Trigger>
                                <Trigger SourceName="PART_EditableTextBox" Property="Text" Value="">
                                    <Setter TargetName="PART_Watermark" Property="Visibility" Value="Visible" />
                                </Trigger>
                                <Trigger SourceName="PART_EditableTextBox" Property="Text" Value="{x:Null}">
                                    <Setter TargetName="PART_Watermark" Property="Visibility" Value="Visible" />
                                </Trigger>
                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </ResourceDictionary>
    </Application.Resources>
</Application>

3)CascadePickerExample.xaml 示例代码如下:

 <controls:CascadePicker
            Width="240"
            Height="40"
            wd:ElementHelper.Watermark="请选择内容"
            DisplayMemberPath="Name"
            Text="{Binding City, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
            ItemsSource="{Binding CityInfos}" />

效果图

以上就是WPF利用CommunityToolkit.Mvvm实现级联选择器的详细内容,更多关于WPF级联选择器的资料请关注脚本之家其它相关文章!

相关文章

  • C#中如何使用 XmlReader 读取XML文件

    C#中如何使用 XmlReader 读取XML文件

    本文介绍了C#中使用XmlReader,只读、向前、循环读取XML节点的方法,并为我们列出、XmlReader类的方法、属性、枚举等成员,希望对大家学习有所帮助。
    2016-05-05
  • c#将不同类型的数据存储到一起的实现

    c#将不同类型的数据存储到一起的实现

    在C#中存储不同类型的数据有多种方式,本文主要介绍了c#将不同类型的数据存储到一起的实现,具有一定的参考价值,感兴趣的可以了解一下
    2025-07-07
  • 使用C#代码在PDF文档中添加、删除和替换图片

    使用C#代码在PDF文档中添加、删除和替换图片

    在当今数字化文档处理场景中,动态操作PDF文档中的图像已成为企业级应用开发的核心需求之一,本文 将介绍如何在.NET平台使用C#代码在PDF文档中添加、删除和替换图片,需要的朋友可以参考下
    2025-04-04
  • C# 7.2中结构体性能问题的解决方案

    C# 7.2中结构体性能问题的解决方案

    这篇文章主要给大家介绍了关于C# 7.2中结构体性能问题的解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-08-08
  • 解析C#网络编程中的Http请求

    解析C#网络编程中的Http请求

    这篇文章主要介绍了C#网络编程中的Http请求,不过这次也使我对C#网络编程了解的更多,算是一次学习经历吧,文章结合示例代码给大家介绍的非常详细,需要的朋友可以参考下
    2022-03-03
  • C#中接口(interface)的理解

    C#中接口(interface)的理解

    C#中接口(interface)的理解...
    2007-03-03
  • 浅谈C#泛型的用处与特点

    浅谈C#泛型的用处与特点

    泛型是 2.0 版 C# 语言和公共语言运行库 (CLR) 中的一个新功能。泛型将类型参数的概念引入 .NET Framework,类型参数使得设计如下类和方法成为可能:这些类和方法将一个或多个类型的指定推迟到客户端代码声明并实例化该类或方法的时候
    2013-09-09
  • C#程序连接数据库及读取数据库中字段的简单方法总结

    C#程序连接数据库及读取数据库中字段的简单方法总结

    包括C#连接Access、Oracle或者SQL Server,这里整理了一些C#连接数据库及从读取数据库中字段的简单方法总结,需要的朋友可以参考下
    2016-05-05
  • C#如何添加PPT背景

    C#如何添加PPT背景

    这篇文章主要为大家详细介绍了C#如何添加PPT背景,添加纯色背景、渐变色背景、图片背景等,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-07-07
  • c#中string的特性介绍及注意事项小结

    c#中string的特性介绍及注意事项小结

    这篇文章主要给大家介绍了关于c#中string的特性介绍及注意事项的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用c#具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-11-11

最新评论