首页 > 其他分享 >WPF树形菜单

WPF树形菜单

时间:2024-09-12 22:13:18浏览次数:1  
标签:菜单 public Header 树形 TreeViewModel new WPF 我们

WPF 保姆级教程怎么实现一个树形菜单

 

先看一下效果吧:

    

我们直接通过改造一下原版的TreeView来实现上面这个效果

我们先创建一个普通的TreeView

代码很简单:

复制代码
        <TreeView>
            <TreeViewItem Header="人事部"/>
            <TreeViewItem Header="技术部">
                <TreeViewItem Header="技术部-1"/>
                <TreeViewItem Header="技术部-1"/>
            </TreeViewItem>
            <TreeViewItem Header="财务部"/>
        </TreeView>
复制代码

实现的效果如下:

如果把这个当成是项目的菜单栏,应该会被领导骂死,一个是不够灵活,数据是写死的;二是样式不好看,只有点文字部分才会展开。

创建一下模板

直接在设计器中右键我们的item,编辑副本,点击确定,我们会得到下面一段代码

里面有一个叫Bd的border,我们把这个border的背景色去掉,然后我们自己去创建两个新的border

复制代码
<Border Background="Transparent" Margin="-200,0,-200,0" Grid.ColumnSpan="4"/>
<Border x:Name="bd1" Background="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" 
        Margin="-200,0,-200,0" Visibility="Hidden" Grid.ColumnSpan="4">
    <Border.Effect>
        <DropShadowEffect BlurRadius="5" ShadowDepth="2"/>
    </Border.Effect>
</Border>
<ToggleButton x:Name="Expander" ClickMode="Press" 
              IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource Mode=TemplatedParent}}" 
              Style="{StaticResource ExpandCollapseToggleStyle}"/>
<Border x:Name="Bd" Grid.Column="1" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
    <ContentPresenter x:Name="PART_Header" ContentSource="Header" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Border>
复制代码

上面红色部分是我们新增的两个border,原本的叫Bd的border,我们只保留紫色部分的属性.

原本的代码里面有两个关于Bd的trigger

我们取名为bd1的border,最开始的Visibility设置的是Hidden,我们替换一下关于Bd的trigger,让它变成当IsSelected是true的情况下,让bd1的Visibility变成Visible.

复制代码
<Trigger Property="IsSelected" Value="true">
    <Setter Property="Visibility" TargetName="bd1" Value="Visible"/>
    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
</Trigger>
<MultiTrigger>
    <MultiTrigger.Conditions>
        <Condition Property="IsSelected" Value="true"/>
        <Condition Property="IsSelectionActive" Value="false"/>
    </MultiTrigger.Conditions>
    <Setter Property="Visibility" TargetName="bd1" Value="Visible"/>
    <Setter Property="Background" TargetName="bd1" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightBrushKey}}"/>
    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}}"/>
复制代码

再运行一下,看一下效果

基本上已经算是成功一半了,但是这个时候,我们的菜单只有一个有效果,其他的还是原来的样式,那是因为我们只有一个TreeViewItem使用了我们写的效果

如果我们每一个TreeViewItem都复制一下这句Style="{DynamicResource TreeViewItemStyle1}" ,是不是显得很呆,而且这只是在我们的菜单很少的情况下,如果菜单很多,这个方法就不可行。

所以这里我们用一个TreeView的ItemContainerStyle来操作一下

        <Style x:Key="treeViewStyle1" TargetType="{x:Type TreeView}" BasedOn="{StaticResource {x:Type TreeView}}">
            <Setter Property="ItemContainerStyle" Value="{StaticResource TreeViewItemStyle1}"/>
        </Style>

我们创建一个类型是TreeView的style,把它的ItemContainerStyle设置成我们之前添加的那个style,然后我们把这个style放到我们的TreeView上

这个时候我们再运行就会发现首级菜单的样式都实现我们想要的效果了,但是子集菜单还是原来的样式

我们在代码里面添加下面一个方法

复制代码
        private void ApplyItemContainerStyle(ItemsControl itemsControl)
        {
            foreach (var item in itemsControl.Items)
            {
                var treeViewItem = item as TreeViewItem;
                if (treeViewItem != null)
                {
                    treeViewItem.Style = treeview1.ItemContainerStyle;
                    ApplyItemContainerStyle(treeViewItem);
                }
            }
        }
复制代码

然后我们在构造函数里面把我们的TreeView当做是参数传进去

这个方法就是把所有的item和item的子项都设置成treeview的ItemContainerStyle;

我们再启动一下项目,就会发现效果是我们想要的效果了

到这里其实大部分效果都实现了,基本上也可以向领导交差了;

但是还缺少一个数据可拓展性和一个图标的功能,我们先看一下数据可拓展性

在平时的项目里面,一般都会有很多个不同的项目,每个项目可能都有好多个菜单,有的项目还想隐藏某一些菜单,我们总不能所有项目都通过visible属性来设置吧

特别是报表功能可能会有几十个,所以我们需要用到一个东西叫数据模板:HierarchicalDataTemplate;

我们先创建一个类

    public class TreeViewModel
    {
        public string Header { get; set; }
        public ObservableCollection<TreeViewModel> Children { get; set; }
    }

然后回到设计器里面,把我们的代码改成下面的代码

复制代码
<TreeView Style="{DynamicResource treeViewStyle1}" x:Name="treeview1">
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate DataType="{x:Type local:TreeViewModel}" ItemsSource="{Binding Children}">
            <StackPanel Height="40" Orientation="Horizontal">
                <TextBlock Text="{Binding Header}" VerticalAlignment="Center"/>
            </StackPanel>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>
复制代码

对比一下红色部分的绑定,和类的属性,就能知道这个数据模板怎么用了.

再到构造函数里面去添加数据

复制代码
public ObservableCollection<TreeViewModel> MenuCollection { get; set; }

public MainWindow()
{
    InitializeComponent();


    MenuCollection = new ObservableCollection<TreeViewModel>()
    {
        new TreeViewModel
        {
            Header = "人事部"
        },
        new TreeViewModel
        {
            Header = "技术部",
            Children = new ObservableCollection<TreeViewModel>
            {
                new TreeViewModel { Header = "技术部-1"},
                new TreeViewModel { Header = "技术部-2"},
            }
        },
        new TreeViewModel
        {
            Header = "财务部",
        },
    };

    treeview1.ItemsSource = MenuCollection;
}
复制代码

注意这两段标红的代码,我们用一个集合MenuCollection模拟一下我们从数据库或者其他地方查询出来的菜单集合,然后把它做为数据源给treeview就可以了

再运行一下项目,它就差不多实现我们想要的效果了,现在再去找领导交差,领导还会夸你做的不错,只是还差一个图标了,这个就是锦上添花的东西了.

我们百度搜索一下  阿里ICON,去到官网里面,创建一个自己的账号,然后搜索一些自己喜欢的图标

 把自己喜欢的图标添加到自己的项目中去,这里的项目名很重要,我取的是  FatSheep

 在到我的项目里面去把这个资源文件下载到自己的项目中

 下载下来的文件,我们把ttf后缀的文件添加到我们的项目里面去

把它作为资源引入到代码里面

<FontFamily x:Key="FatSheep">pack:application:,,,/自己项目的名字;component/Resources/iconfont.ttf#FatSheep</FontFamily>

记得修改一下自己的项目名字,我取的是TreeViewDemo,改成自己的项目名就好了,最后的结尾,是FatSheep,记得改成自己的ICON项目名称

接着我们在TreeViewModel里面添加一个Icon属性

复制代码
    public class TreeViewModel
    {
        public string Header { get; set; }
        public string Icon { get; set; }
        public ObservableCollection<TreeViewModel> Children { get; set; }
    }
复制代码

然后我们在数据源里面添加一下数据

复制代码
MenuCollection = new ObservableCollection<TreeViewModel>()
{
    new TreeViewModel
    {
        Header = "人事部",
        Icon = "\ue71c"
    },
    new TreeViewModel
    {
        Header = "技术部",
        Icon = "\ue71c",
        Children = new ObservableCollection<TreeViewModel>
        {
            new TreeViewModel { Header = "技术部-1", Icon="\ue71c"},
            new TreeViewModel { Header = "技术部-2" , Icon="\ue71c"},
        }
    },
    new TreeViewModel
    {
        Header = "财务部",
        Icon = "\ue71c"
    },
};
复制代码

设计器里面添加一下显示部分的代码

复制代码
<TreeView Style="{StaticResource treeViewStyle1}" x:Name="treeView1" BorderThickness="0,0,1,0" Grid.Column="1">
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate DataType="{x:Type local:TreeViewModel}" ItemsSource="{Binding Children}">
            <StackPanel Height="40" Orientation="Horizontal">
                <TextBlock Text="{Binding Icon}" VerticalAlignment="Center" FontFamily="{StaticResource FatSheep}" Margin="0,0,5,0"/>
                <TextBlock Text="{Binding Header}" VerticalAlignment="Center"/>
            </StackPanel>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>
复制代码

再启动项目,功能就完成了

这个笑脸是怎么来的了

那是因为我自己的项目里面添加了一个笑脸

我们复制一下这个代码,   &#xe71c;  我们把它改成 \ue71c,这是一个转义字符,就这样我们就能添加如何自己喜欢的图标了。

 

 

项目github地址:bearhanQ/WPFFramework: Share some experience (github.com)

QQ技术交流群:332035933;

欢迎进群讨论问题,不论是winform,还是wpf,还是.net core的,还有很多萌妹.

标签:菜单,public,Header,树形,TreeViewModel,new,WPF,我们
From: https://www.cnblogs.com/Leo_wl/p/18411213

相关文章

  • WPF 已知问题 在 WIC 层处理异常图片时 可能由于出现未处理异常导致进程退出
    本文记录一个已知问题,此问题预计和WPF只有一毛钱关系,本质问题是在WIC层的WindowsCodecs.dll或CLR层上。在一些奇怪的系统上,解码一些奇怪的图片时,可能在解码器层抛出未捕获的本机异常,从而导致进程退出我使用ProcDump工具抓到了一台服务器上WPF应用程序打开某个图片文......
  • WPF 冷知识 定义依赖属性的最大数量是 65534 个
    远古的WPF框架开发的大佬们认为没有任何业务的开发者需要用到超过65534个依赖属性和附加属性,为了节省内存空间就限制了所有的依赖属性和附加属性的定义总和加起来不能大于等于65535个似乎大家可能对65535个依赖属性的定义量没有概念,这么说,即使只是将这些依赖属性定义出来......
  • WPF 什么时候 VisualTreeHelper.GetDescendantBounds 将返回无穷大
    本文将和大家介绍在什么情况下WPF将会在调用VisualTreeHelper.GetDescendantBounds方法时,返回一个无穷大的范围尺寸在WPF的容器控件的里层元素的RenderTransform包含NaN将会导致对上层容器调用VisualTreeHelper.GetDescendantBounds返回无穷大返回的矩形范围是-∞,......
  • WPF 已知问题 开启 WM_Pointer 消息之后 获取副屏触摸数据坐标偏移
    本文记录WPF触摸的一个已知问题,仅在开启WM_Pointer消息之后,将应用程序运行在包含多个屏幕的带触摸屏的设备上,如此时在非主屏幕的触摸屏上进行触摸,使用GetStylusPoint或GetIntermediateTouchPoints方法获取触摸点时,将会发现所获取的触摸点的坐标是偏的,偏的坐标差值刚好是整......
  • WPF 已知问题 包含 NaN 的 Geometry 几何可能导致渲染层抛出 UCEERR_RENDERTHREADFAIL
    本文记录一个WPF已知问题,当传入到渲染的Geometry几何里面包含了NaN数值,将可能让应用程序收到从渲染层抛上来的UCEERR_RENDERTHREADFAILURE异常,且此异常缺乏必要信息,比较难定位到具体错误逻辑此问题是小伙伴报告给我的,详细请看https://github.com/dotnet/wpf/issues/7421......
  • WPF 尝试使用 WinML 做一个简单的手写数字识别应用
    最近我看了微软的AI训练营之后,似乎有点了解WindowsMachineLearning和DirectML的概念,于是我尝试实践一下,用WPF写一个简单的触摸手写输入的画板,再使用大佬训练好的mnist.onnx模型,对接WinML实现一个简单的手写数字识别应用本文属于WinML的入门级博客,我将尝试一步步......
  • WPF 的 WriteableBitmap 在 Intel 11 代 Iris Xe Graphics 核显设备上停止渲染
    在Intel11代锐炬Intel®Iris®XeGraphics核显设备上,如果此设备使用旧版本驱动,则可能导致WPF的WriteableBitmap停止渲染。此问题和WPF无关,此问题是Intel的bug且最新驱动版本已修复官方问题记录地址:https://www.intel.cn/content/www/cn/zh/support/articles/000......
  • WPF 的 Viewport3D 等 3D 模块在带 Intel UHD 770 设备上抛出渲染异常
    在带IntelUHD770的设备上,使用旧版本驱动,即小于30.0.101.1660版本驱动,将会导致WPF的3D模块出现渲染异常。此问题和WPF无关,此问题是Intel的bug且最新驱动版本已修复官方问题记录地址:https://community.intel.com/t5/Graphics/Crash-with-UHD-770-in-WPF-applicatio......
  • 【转】[C#][WPF] 避免窗口最大化时遮盖任务栏
    转自:https://learn.microsoft.com/zh-cn/previous-versions/msdn10/dd366102(v=MSDN.10)WPF窗口最大化时有个很不好的现象是:如果窗口的WindowStyle被直接或间接地设置为None后(比如很多情况下你会覆盖默认的窗体样式,即不采用Windows默认的边框和最大化最等按钮,来打造个性的窗......
  • 树形多选
     <scriptsetuplang="ts">import{useVModel}from'@vueuse/core';constprops=defineProps({modelValue:{type:Object,default:()=>{},},});/*AI视觉应用*/constemit=defineEmits(['update:modelValu......