[Aaronyang] 写给自己的WPF4.5 笔记10[层次数据需求处理,TreeView绿色文章1/4]

 我的文章一定要做到对读者负责,否则就是失败的文章  ---------   www.ayjs.net    aaronyang技术分享

AY留言:

  文章根据难易,我根据游戏的规则进行了分色,希望读者能选择自己的能力去读。白色<绿色<蓝色<紫色<橙色<红色

博文摘要:

  1. 简单的TreeView静态写法,了解展开事件,选中事件
  2. 关于磁盘驱动器的图标的获得,文件夹的图标的获得,文件的图标的获得,系统自己shell32.dll的图标的获得(例如我的电脑,回收站等icon)
  3. 关于TreeView的HierarchicalDataTemplate模板的讲解
  4. 实战DEMO,简单的资源管理器,非一次性读取所有目录,然后绑定的,是点击一次才加载目录。

       [Aaronyang] 写给自己的WPF4.5 笔记10[层次数据需求处理,TreeView绿色文章1/4]

       5.DEMO下载,下载地址:http://pan.baidu.com/s/1mg858bI

 

  =============潇洒的版权线==========www.ayjs.net===== Aaronyang ========= AY =========== 安徽 六安 杨洋 ==========   未经允许不许转载 =========

 

1. 基本TreeView,静态写法(非后台绑定数据,主要理解原理和结构,方便动态写法更好的思路和理解)

  基本Tree   难度★

<Window x:Class="TemplateDemo.Window3"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Ay WPF treeview" Height="600" Width="1000" Loaded="Window_Loaded">
    <Canvas>
        <TreeView x:Name="tvDemo1_1">
            <TreeViewItem Header="根节点">
                <TreeViewItem Header="节点1_1">
                    <TreeViewItem Header="节点1_1_1"></TreeViewItem>
                    <TreeViewItem Header="节点1_1_2"></TreeViewItem>
                    <TreeViewItem Header="节点1_1_3"></TreeViewItem>
                </TreeViewItem>
                <TreeViewItem Header="节点2">
                    <TreeViewItem Header="节点2_1_1"></TreeViewItem>
                    <TreeViewItem Header="节点2_1_2"></TreeViewItem>
                    <TreeViewItem Header="节点2_1_3"></TreeViewItem>
                </TreeViewItem>
                <TreeViewItem Header="节点3"></TreeViewItem>
                <TreeViewItem Header="节点4"></TreeViewItem>
            </TreeViewItem>
        </TreeView>
        <TreeView x:Name="tvDemo1_2" Canvas.Left="154">
            <TreeViewItem Header="根节点1">
                <TreeViewItem Header="节点1_1">
                    <TreeViewItem Header="节点1_1_1"></TreeViewItem>
                    <TreeViewItem Header="节点1_1_2"></TreeViewItem>
                    <TreeViewItem Header="节点1_1_3"></TreeViewItem>
                </TreeViewItem>
                <TreeViewItem Header="节点2">
                    <TreeViewItem Header="节点2_1_1"></TreeViewItem>
                    <TreeViewItem Header="节点2_1_2"></TreeViewItem>
                    <TreeViewItem Header="节点2_1_3"></TreeViewItem>
                </TreeViewItem>
                <TreeViewItem Header="节点3"></TreeViewItem>
                <TreeViewItem Header="节点4"></TreeViewItem>
            </TreeViewItem>
            <TreeViewItem Header="根节点2">
                <TreeViewItem Header="节点1"></TreeViewItem>
                <TreeViewItem Header="节点2"></TreeViewItem>
                <TreeViewItem Header="节点3"></TreeViewItem>
                <TreeViewItem Header="节点4"></TreeViewItem>
            </TreeViewItem>
            <TreeViewItem Header="根节点3">
                <TreeViewItem Header="节点1"></TreeViewItem>
                <TreeViewItem Header="节点2"></TreeViewItem>
                <TreeViewItem Header="节点3"></TreeViewItem>
                <TreeViewItem Header="节点4"></TreeViewItem>
            </TreeViewItem>
        </TreeView>
    </Canvas>
</Window>

一个根节点,或者多个根节点,跟代码结构有关,Header显示文字

  [Aaronyang] 写给自己的WPF4.5 笔记10[层次数据需求处理,TreeView绿色文章1/4]

展开事件(展开时候,我们在它下面增加一个"ay展开测试"TreeViewItem节点),满足那些异步加载的需求,点击展开获取数据,然后绑定

 <TreeView x:Name="tvDemo1_1" TreeViewItem.Expanded="tvDemo1_1_Expanded">
       int index =1;
        private void tvDemo1_1_Expanded(object sender, RoutedEventArgs e)
        {
            //ay 获得被单击的节点
            TreeViewItem item = (TreeViewItem)e.OriginalSource;
            item.Items.Add(new TreeViewItem { Header = "Ay展开"+index });
            index++;
        }

[Aaronyang] 写给自己的WPF4.5 笔记10[层次数据需求处理,TreeView绿色文章1/4]

增加单击事件,满足菜单数导航的需求,我们可以把想要的参数放到ListViewItem的Tag中去  TreeViewItem.Selected

  <TreeView x:Name="tvDemo1_1" TreeViewItem.Expanded="tvDemo1_1_Expanded" TreeViewItem.Selected="tvDemo1_1_Selected">
            <TreeViewItem Header="根节点">
                <TreeViewItem Header="节点1_1">
                    <TreeViewItem Header="节点1_1_1" Tag="1.1.1"></TreeViewItem>
                    <TreeViewItem Header="节点1_1_2" Tag="1.1.2"></TreeViewItem>
                    <TreeViewItem Header="节点1_1_3" Tag="1.1.3"></TreeViewItem>
                </TreeViewItem>
                <TreeViewItem Header="节点2">
                    <TreeViewItem Header="节点2_1_1"></TreeViewItem>
                    <TreeViewItem Header="节点2_1_2"></TreeViewItem>
                    <TreeViewItem Header="节点2_1_3"></TreeViewItem>
                </TreeViewItem>
                <TreeViewItem Header="节点3"></TreeViewItem>
                <TreeViewItem Header="节点4"></TreeViewItem>
            </TreeViewItem>
        </TreeView>
   private void tvDemo1_1_Selected(object sender, RoutedEventArgs e)
        {
            //ay 获得被单击的节点
            TreeViewItem item = (TreeViewItem)e.OriginalSource;
            if (item.Tag != null)
            {
                MessageBox.Show(item.Tag.ToString());
            }

        }

效果图:TreeViewItem有Tag就弹出来,没有就不弹窗

[Aaronyang] 写给自己的WPF4.5 笔记10[层次数据需求处理,TreeView绿色文章1/4]

 

2. 后台绑定树状的数据,主要是为了理解HierarchicalDataTemplate

为了更好的演示增删改查,我们的实体需要继承属性通知,我新建了个窗体,没有在上面的窗体上继续写了。上面都只是热身

需求模拟: 目录与文件,为什么选这个,因为我不需要创建太多的数据源而浪费时间,类似需求:部门与员工

我们打开我的电脑,发现左侧目录树是带调整宽度的,第一反应想到了GridSpliter,那就用Grid布局了

 2.1画出基本布局DEMO

<Window x:Class="TemplateDemo.Window4"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="AY 目录树" Height="600" Width="1000">
    <Grid x:Name="LayoutRoot" ShowGridLines="false">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" MinWidth="156"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" MinHeight="80"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Border BorderBrush="#CEDAE1" BorderThickness="0,0,0,1" Grid.ColumnSpan="3" Grid.Row="0">
            <Grid Background="#fff" Margin="5">
                <TextBlock Text="导航菜单栏" HorizontalAlignment="Center" VerticalAlignment="Center"></TextBlock>
            </Grid>
        </Border>
        <StackPanel Grid.Column="0" Grid.Row="1" HorizontalAlignment="Left" Margin="5,15,5,15">
            <TreeView x:Name="tvDirectoryDemo" BorderThickness="0">
                <TreeViewItem Header="根节点">
                    <TreeViewItem Header="节点1_1">
                        <TreeViewItem Header="节点1_1_1" Tag="1.1.1">
                        </TreeViewItem>
                        <TreeViewItem Header="节点1_1_2" Tag="1.1.2">
                            <TreeViewItem Header="节点1_1">
                                <TreeViewItem Header="节点1_1_1" Tag="1.1.1">
                                </TreeViewItem>
                                <TreeViewItem Header="节点1_1_2" Tag="1.1.2"></TreeViewItem>
                                <TreeViewItem Header="节点1_1_3" Tag="1.1.3"></TreeViewItem>
                            </TreeViewItem>
                            <TreeViewItem Header="节点2">
                                <TreeViewItem Header="节点2_1_1"></TreeViewItem>
                                <TreeViewItem Header="节点2_1_2"></TreeViewItem>
                                <TreeViewItem Header="节点2_1_3"></TreeViewItem>
                            </TreeViewItem>
                            <TreeViewItem Header="节点3"></TreeViewItem>
                            <TreeViewItem Header="节点4"></TreeViewItem>
                        </TreeViewItem>
                        <TreeViewItem Header="节点1_1_3" Tag="1.1.3"></TreeViewItem>
                    </TreeViewItem>
                    <TreeViewItem Header="节点2">
                        <TreeViewItem Header="节点2_1_1"></TreeViewItem>
                        <TreeViewItem Header="节点2_1_2"></TreeViewItem>
                        <TreeViewItem Header="节点2_1_3"></TreeViewItem>
                    </TreeViewItem>
                    <TreeViewItem Header="节点3"></TreeViewItem>
                    <TreeViewItem Header="节点4"></TreeViewItem>
                </TreeViewItem>
            </TreeView>
        </StackPanel>
        <GridSplitter Grid.Column="1" Grid.Row="1" ShowsPreview="True"  HorizontalAlignment="Left" Width="1" Background="#CEDAE1" VerticalAlignment="Stretch"/>
        <Grid Background="#EEEEEE" Margin="5,15,5,15" Grid.Column="2" Grid.Row="1">
            <TextBlock Text="内容区" HorizontalAlignment="Center" VerticalAlignment="Center"></TextBlock>
        </Grid>
    </Grid>
</Window>

效果图:

[Aaronyang] 写给自己的WPF4.5 笔记10[层次数据需求处理,TreeView绿色文章1/4]

动态绑定数据,我们留下基本的treeview如下,去掉Item

 <TreeView x:Name="tvDirectoryDemo" BorderThickness="0">
 </TreeView>

后台创建数据并绑定ItemsSource

第一步:如何拿到本地电脑的磁盘信息DriveInfo类,我们简单创建Item

[Aaronyang] 写给自己的WPF4.5 笔记10[层次数据需求处理,TreeView绿色文章1/4]

 private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            tvDirectoryDemo.Items.Clear();

            foreach (DriveInfo drive in DriveInfo.GetDrives())
            {

                if (drive.IsReady)
                {
                    TreeViewItem item = new TreeViewItem();
                    item.Tag = drive;
                    item.Header = String.Format("{0}({1})", drive.VolumeLabel == "" ? "本地磁盘" : drive.VolumeLabel, GetDriveClearName(drive.Name));
                    item.Items.Add("*");
                    tvDirectoryDemo.Items.Add(item);
                }


            }
        }
        private string GetDriveClearName(string driveName)
        {
            int mao = driveName.IndexOf(":")+1;
            return driveName.Substring(0, mao);
        }

效果图:

[Aaronyang] 写给自己的WPF4.5 笔记10[层次数据需求处理,TreeView绿色文章1/4]

到目前为止,我们还无法拿到图标,并显示,百度一下C#获得磁盘驱动器图标,网上还是有很多例子的,我们先获得图标

我们在内容区拉取一个Image控件,随便取个名字,后台使用帮手类获得驱动器的图标。

[Aaronyang] 写给自己的WPF4.5 笔记10[层次数据需求处理,TreeView绿色文章1/4]
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace Ay.Framework.WPF.Helpers
{
    public class SystemInfoIcon
    {

        [DllImport("Shell32.dll")]
        private static extern IntPtr SHGetFileInfo
        (
            string pszPath,
            uint dwFileAttributes,
            out   SHFILEINFO psfi,
            uint cbfileInfo,
            SHGFI uFlags
        );


        [StructLayout(LayoutKind.Sequential)]
        private struct SHFILEINFO
        {
            public SHFILEINFO(bool b)
            {
                hIcon = IntPtr.Zero; iIcon = 0; dwAttributes = 0; szDisplayName = ""; szTypeName = "";
            }
            public IntPtr hIcon;
            public int iIcon;
            public uint dwAttributes;
            [MarshalAs(UnmanagedType.LPStr, SizeConst = 260)]
            public string szDisplayName;
            [MarshalAs(UnmanagedType.LPStr, SizeConst = 80)]
            public string szTypeName;
        };

        private enum SHGFI
        {
            SmallIcon = 0x00000001,
            LargeIcon = 0x00000000,
            Icon = 0x00000100,
            DisplayName = 0x00000200,
            Typename = 0x00000400,
            SysIconIndex = 0x00004000,
            UseFileAttributes = 0x00000010
        }

        /// <summary>  
        /// 根据文件扩展名得到系统扩展名的图标  
        /// </summary>  
        /// <param name="fileName">文件名(如:win.rar;setup.exe;temp.txt)</param>  
        /// <param name="largeIcon">图标的大小</param>  
        /// <returns></returns>  
        public static Icon GetFileIcon(string fileName, bool largeIcon)
        {
            SHFILEINFO info = new SHFILEINFO(true);
            int cbFileInfo = Marshal.SizeOf(info);
            SHGFI flags;
            if (largeIcon)
                flags = SHGFI.Icon | SHGFI.LargeIcon | SHGFI.UseFileAttributes;
            else
                flags = SHGFI.Icon | SHGFI.SmallIcon | SHGFI.UseFileAttributes;
            IntPtr IconIntPtr = SHGetFileInfo(fileName, 256, out info, (uint)cbFileInfo, flags);
            if (IconIntPtr.Equals(IntPtr.Zero))
                return null;
            return Icon.FromHandle(info.hIcon);
        }

        /// <summary>    
        /// 获取文件夹图标  
        /// </summary>    
        /// <returns>图标</returns>    
        public static Icon GetDirectoryIcon(string dirName,bool largeIcon)
        {
            SHFILEINFO _SHFILEINFO = new SHFILEINFO();
            int cbFileInfo = Marshal.SizeOf(_SHFILEINFO);
            SHGFI flags;
            if (largeIcon)
                flags = SHGFI.Icon | SHGFI.LargeIcon;
            else
                flags = SHGFI.Icon | SHGFI.SmallIcon;

            IntPtr IconIntPtr = SHGetFileInfo(dirName, 0, out _SHFILEINFO, (uint)cbFileInfo, flags);
            if (IconIntPtr.Equals(IntPtr.Zero))
                return null;
            Icon _Icon = System.Drawing.Icon.FromHandle(_SHFILEINFO.hIcon);
            return _Icon;
        }

    }
}
获得系统信息的图标

接着icon转为imagesource

           /// <summary>
            /// 转换ICON为ImageSource
            /// 二〇一五年二月二日 13:56:03 aaronyang
            /// </summary>
            /// <param name="icon">icon资源</param>
            /// <returns></returns>
            public static ImageSource ToIcon2ImageSource(this Icon icon)
            {
                //Arguments checking
                if (icon == null)
                    throw new ArgumentNullException("icon", "The icon can not be null.");

                ImageSource imageSource = Imaging.CreateBitmapSourceFromHIcon(
                    icon.Handle, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());

                return imageSource;
            }

窗体加载时候,获得图标源,并显示在Image控件中,我们获得C盘的图标

   imgIcon.Source = SystemInfoIcon.GetDirectoryIcon("c:", true).ToIcon2ImageSource() ;

运行后显示图片[Aaronyang] 写给自己的WPF4.5 笔记10[层次数据需求处理,TreeView绿色文章1/4]

这就说明我们在Treeview的模板中使用Image控件就可以显示图标了。删掉测试的图标控件(不好意思,有洁癖),接下来,我们新建个DirectoryTreeViewItem类,模拟每行TreeViewItem可能需要的值

public class DirectoryTreeViewItem : INotifyPropertyChanged
    {
        public DirectoryTreeViewItem()
        {
            Children = new ObservableCollection<DirectoryTreeViewItem>();
        }
        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, e);
            }
        }

        public ObservableCollection<DirectoryTreeViewItem> Children;


        /// <summary>
        /// 磁盘C,d,e等
        /// </summary>
        private string driveName;
        public string DriveName
        {
            get { return driveName; }
            set
            {
                driveName = value;
                OnPropertyChanged(new PropertyChangedEventArgs("DriveName"));
            }
        }

        /// <summary>
        /// 卷标名称
        /// </summary>
        private string labelName;
        public string LabelName
        {
            get { return labelName; }
            set
            {
                labelName = value;
                OnPropertyChanged(new PropertyChangedEventArgs("LabelName"));
            }
        }


        /// <summary>
        /// 有的是驱动1,有的是目录2,有的是文件3
        /// </summary>
        private string driveType;
        public string DriveType
        {
            get { return driveType; }
            set
            {
                driveType = value;
                OnPropertyChanged(new PropertyChangedEventArgs("DriveType"));
            }
        }

        /// <summary>
        /// 显示图标
        /// </summary>
        private ImageSource diskIcon;
        public ImageSource DiskIcon
        {
            get { return diskIcon; }
            set
            {
                diskIcon = value;
                OnPropertyChanged(new PropertyChangedEventArgs("DiskIcon"));
            }
        }


    }

接下来,需要拿到我的计算机,win8中叫这台电脑的 图标,百度C# 系统自带图标,这个我找了好久,自己试出来的

[DllImport("Shell32.dll")] //调用系统动态链接库
        public static extern int ExtractIcon(IntPtr h, string strx, int ii);//获取句柄,shell32.dll文件位置,shell32图片的序号 全部提取就0-277
        /// 功能: 
        /// 得到系统图标,诸如文件夹,桌面图标 
        /// 参数: 
        /// int nIndex 指定图标的索引,可取如下值 
        /// .   0 默认图标 
        /// .   1 默认的   .doc   图标 * 
        /// .   2 可执行文件图标 
        /// .   3 关闭的文件夹图标 
        /// .   4 打开的文件夹图标 
        /// .   5 5.25 ‘   驱动器图标 
        /// .   6 3.5 ‘   驱动器图标 
        /// .   7 可移动的驱动器图标 
        /// .   8 硬盘驱动器图标 
        /// .   9 网络驱动器图标 
        /// .   10 断开的网络驱动器图标 
        /// .   11 CD-ROM驱动器图标 
        /// .   12 RAM驱动器图标 
        /// .   13 整个网络图标 
        /// .   14 网络连接图标 u 
        /// .   15 网络工作站图标   
        /// .   16 本地打印机图标 * 
        /// .   17 网络图标 u 
        /// .   18 网络工作组图标 u 
        /// .   19 程序组图标 s 
        /// .   20 文档图标 s 
        /// .   21 设置图标 s 
        /// .   22 查找图标 s 
        /// .   23 帮助图标 s 
        /// .   24 运行图标 s 
        /// .   25 睡眠图标 s 
        /// .   26 Docking   Station   图标u 
        /// .   27 关机图标 s 
        /// .   28 共享图标 t 
        /// .   29 快捷方式的箭头图标 t 
        /// .   30 大箭头图标 u 
        /// .   31 空回收站图标 * 
        /// .   32 满的回收站图标 * 
        /// .   33 拨号网络图标 * 
        /// .   34 桌面图标 
        /// .   35 控制台图标 * 
        /// .   36 程序组图标 s 
        /// .   37 打印机文件夹图标 * 
        /// .   38 字体文件夹图标 * 
        /// .   39 Windows旗帜图标 * 
        /// .   40 Audio   CD   图标           
        /// 后面标有符号的说明有特殊用法:   
        /// *   这些图标可以在注册表的其他地方的设置。 
        /// t   这些图标必须是空白背景。   
        /// s   这些图标将用在开始菜单上。   
        /// u   这些图标可能并没有使用或不能通过注册表修改 
        /// 返回 
        /// 图标的句柄,失败返回NULL 
        public static Icon AyExtractIcon(string FileName, int iIndex)
        {
            //抽取我的电脑的图标
            IntPtr intp = new IntPtr();
            IntPtr hIcon = (IntPtr)ExtractIcon(intp, FileName, iIndex);
            if (!hIcon.Equals(null))
            {
                Icon icon = System.Drawing.Icon.FromHandle(hIcon);
                return icon;
            }
            return null;
        }

使用:15是我的计算机图标

SystemInfoIcon.AyExtractIcon("%SystemRoot%\\system32\\shell32.dll", 15)

 

接下来使用层次的数据模板

 

 永远记得这一次的失误。。。。。。我的浏览器自动关了,以下内容是重写的。可能心情有点不太一样,具体请参考思想吧。可能代码有些理解别扭,请留言即可,我会去改进。好饿啊,我要回家吃饭了二〇一五年二月二日 21:04:10


 

关于HierarchicalDataTemplate数据模板

   <StackPanel Grid.Column="0" Grid.Row="1" HorizontalAlignment="Left" Margin="5,15,5,15">
            <TreeView x:Name="tvDirectoryDemo" BorderThickness="0" MinWidth="156" TreeViewItem.Expanded="tvDirectoryDemo_Expanded" Height="700">
                <TreeView.ItemTemplate>
                    <HierarchicalDataTemplate DataType="{x:Type local:DirectoryTreeViewItem}" ItemsSource="{Binding Path=Children}">
                        <StackPanel Orientation="Horizontal">
                            <Image VerticalAlignment="Center" Source="{Binding DiskIcon}" Width="16" Height="16" Margin="0,0,2,0"></Image>
                            <TextBlock Padding="2" Text="{Binding ItemHeader}"></TextBlock>
                        </StackPanel>
                    </HierarchicalDataTemplate>
                </TreeView.ItemTemplate>
            </TreeView>
        </StackPanel>

我们还记得在数据模板那一篇博客中讲到,指定了DataType,那么窗口中的,只要是这个数据对象,就会以这个数据模板展示

[Aaronyang] 写给自己的WPF4.5 笔记10[层次数据需求处理,TreeView绿色文章1/4]

那么如果有多个实体对象,例如 目录和产品, 部门和员工等2个对象,当然还有3个对虾给你以上的。我们就可以定义多个HierarchicalDataTemplate 

例如下面的代码,你可以参考,目录对象包含 产品对象,但是每个TreeViewItem都一样的,由于DataType不一样,所以展示的结果也就不一样了,HierarchicalDataTemplate很奇妙,需要在具体应用中才能更好的理解

 <HierarchicalDataTemplate DataType="{x:Type data:Category}"
                                ItemsSource="{Binding Path=Products}">
      <TextBlock Text="{Binding Path=CategoryName}"/>
    </HierarchicalDataTemplate>
    <HierarchicalDataTemplate DataType="{x:Type data:Product}"      >
      <TextBlock Text="{Binding Path=ModelName}" />      
    </HierarchicalDataTemplate>
    

接下来我们增加样式样式,由于本章主要讲解treeview,非样式,由于时间关系,在网上又找到了,我想要的样式,地址查看

关于这个我已经封装好了 Ay.Framework.WPF.dll 版本1.1    下载地址:http://pan.baidu.com/s/1kTFZof9

 使用方法:首先引入Ay.Framework.WPF.dll 版本1.1,然后你需要创建一个资源

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:cw="clr-namespace:Ay.Framework.WPF;assembly=Ay.Framework.WPF"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <!-- 悬停状态的画刷 -->
    <SolidColorBrush x:Key="HoverBackgroundBrushKey" Color="#E5F3FB" />
    <SolidColorBrush x:Key="HoverBorderBrushKey" Color="#70C0E7" />

    <!-- 选中(激活)状态的画刷 -->
    <SolidColorBrush x:Key="SelectedActiveBackgroundBrushKey" Color="#CBE8F6" />
    <SolidColorBrush x:Key="SelectedActiveBorderBrushKey" Color="#26A0DA" />

    <!-- 选中(悬停)状态的画刷 -->
    <SolidColorBrush x:Key="SelectedHoverBackgroundBrushKey" Color="#D1E8FF" />
    <SolidColorBrush x:Key="SelectedHoverBorderBrushKey" Color="#66A7E8" />

    <!-- 选中(失效)状态的画刷 -->
    <SolidColorBrush x:Key="SelectedInactiveBackgroundBrushKey" Color="#F7F7F7" />
    <SolidColorBrush x:Key="SelectedInactiveBorderBrushKey" Color="#DEDEDE" />

    <!-- TreeViewItem 的展开箭头 -->
    <PathGeometry x:Key="TreeArrow" Figures="M0,0 L0,5 L5,0 z" />
    <Style x:Key="ExpandCollapseToggleStyle" TargetType="{x:Type ToggleButton}">
        <Setter Property="Focusable" Value="False" />
        <Setter Property="Width" Value="7" />
        <Setter Property="Height" Value="16" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ToggleButton}">
                    <Border Background="Transparent" Width="7" Height="16" Padding="0,5,0,0">
                        <Path x:Name="ExpandPath" Fill="Transparent" Stroke="#989898"
                              Data="{StaticResource TreeArrow}">
                            <Path.RenderTransform>
                                <RotateTransform Angle="135" CenterX="2.5" CenterY="2.5" />
                            </Path.RenderTransform>
                        </Path>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter TargetName="ExpandPath" Property="Stroke" Value="#1BBBFA" />
                            <Setter TargetName="ExpandPath" Property="Fill" Value="Transparent" />
                        </Trigger>
                        <Trigger Property="IsChecked" Value="True">
                            <Setter TargetName="ExpandPath" Property="RenderTransform">
                                <Setter.Value>
                                    <RotateTransform Angle="180" CenterX="3" CenterY="3" />
                                </Setter.Value>
                            </Setter>
                            <Setter TargetName="ExpandPath" Property="Stroke" Value="#262626" />
                            <Setter TargetName="ExpandPath" Property="Fill" Value="#595959" />
                        </Trigger>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="IsChecked" Value="True" />
                                <Condition Property="IsMouseOver" Value="True" />
                            </MultiTrigger.Conditions>
                            <Setter TargetName="ExpandPath" Property="Stroke" Value="#1BBBFA" />
                            <Setter TargetName="ExpandPath" Property="Fill" Value="#82DFFB" />
                        </MultiTrigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <!-- TreeViewItem 样式 -->
    <Style x:Key="{x:Type TreeViewItem}" TargetType="{x:Type TreeViewItem}">
        <Setter Property="BorderThickness" Value="1" />
        <Setter Property="Padding" Value="0" />
        <Setter Property="IsExpanded" Value="{Binding IsExpanded}"></Setter>
        <Setter Property="Tag" Value="{Binding DirTag}"></Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type TreeViewItem}">
                    <ControlTemplate.Resources>
                        <!-- 计算节点缩进的转换器 -->
                        <cw:IndentConverter Indent="12" MarginLeft="5" x:Key="IndentConverter" />
                    </ControlTemplate.Resources>
                    <StackPanel>
                        <Border x:Name="Border"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}"
                                Background="{TemplateBinding Background}"
                                Padding="{TemplateBinding Padding}"
                                SnapsToDevicePixels="True">
                            <Grid Margin="{Binding Converter={StaticResource IndentConverter}, RelativeSource={RelativeSource TemplatedParent}}">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition MinWidth="12" Width="Auto" />
                                    <ColumnDefinition />
                                </Grid.ColumnDefinitions>
                                <ToggleButton x:Name="Expander"
                                              Style="{StaticResource ExpandCollapseToggleStyle}"
                                              IsChecked="{Binding Path=IsExpanded,RelativeSource={RelativeSource TemplatedParent}}"
                                              ClickMode="Press" Width="Auto" HorizontalAlignment="Left" 
                                              Height="Auto" Margin="1,0,0,0" />
                                <ContentPresenter x:Name="PART_Header"
                                                  Grid.Column="1"
                                                  ContentSource="Header"
                                                  HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                            </Grid>
                        </Border>
                        <ItemsPresenter x:Name="ItemsHost" />
                    </StackPanel>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsExpanded" Value="False">
                            <Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed" />
                        </Trigger>
                        <Trigger Property="HasItems" Value="False">
                            <Setter TargetName="Expander" Property="Visibility" Value="Hidden" />
                        </Trigger>
                        <Trigger Property="IsSelected" Value="True">
                            <Setter TargetName="Border" Property="BorderBrush"
                                    Value="{StaticResource SelectedActiveBorderBrushKey}" />
                            <Setter TargetName="Border" Property="Background"
                                    Value="{StaticResource SelectedActiveBackgroundBrushKey}" />
                        </Trigger>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="IsSelected" Value="True" />
                                <Condition Property="Selector.IsSelectionActive" Value="False" />
                            </MultiTrigger.Conditions>
                            <Setter TargetName="Border" Property="BorderBrush"
                                    Value="{StaticResource SelectedInactiveBorderBrushKey}" />
                            <Setter TargetName="Border" Property="Background"
                                    Value="{StaticResource SelectedInactiveBackgroundBrushKey}" />
                        </MultiTrigger>
                        <Trigger SourceName="Border" Property="IsMouseOver" Value="True">
                            <Setter TargetName="Border" Property="BorderBrush"
                                    Value="{StaticResource HoverBorderBrushKey}" />
                            <Setter TargetName="Border" Property="Background"
                                    Value="{StaticResource HoverBackgroundBrushKey}" />
                        </Trigger>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="IsSelected" Value="True" />
                                <Condition SourceName="Border" Property="IsMouseOver" Value="True" />
                            </MultiTrigger.Conditions>
                            <Setter TargetName="Border" Property="BorderBrush"
                                    Value="{StaticResource SelectedHoverBorderBrushKey}" />
                            <Setter TargetName="Border" Property="Background"
                                    Value="{StaticResource SelectedHoverBackgroundBrushKey}" />
                        </MultiTrigger>
                        <Trigger Property="IsEnabled" Value="False">
                            <Setter Property="Foreground" 
                                    Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

接着在app.xaml合并资源

<Application x:Class="TemplateDemo.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:TemplateDemo"
             StartupUri="Window4.xaml">
  
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/TemplateDemo;component/Themes/TreeView.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>     
    </Application.Resources>
</Application>

我在原作者的基础上,增加了以下代码,为了方便绑定TreeViewItem的是否展开样式设置

        <Setter Property="IsExpanded" Value="{Binding IsExpanded}"></Setter>
        <Setter Property="Tag" Value="{Binding DirTag}"></Setter>

在DirectoryTreeViewItem类中增加了IsExpanded是否展开的属性,跟前端对应的

接着我们需要在后台读取硬盘的信息,创建一个数据源用于展示

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {

            tvDirectoryDemo.Items.Clear();
            ObservableCollection<DirectoryTreeViewItem> dirTree = new ObservableCollection<DirectoryTreeViewItem>();
            DirectoryTreeViewItem root = new DirectoryTreeViewItem();
            root.LabelName = "我的电脑";
            root.DirTag = null;
            root.DiskIcon = SystemInfoIcon.AyExtractIcon("%SystemRoot%\\system32\\shell32.dll", 15).ToIcon2ImageSource();
            root.DriveName = "";
            root.DriveType = "pc";
            root.IsExpanded = true;
            foreach (DriveInfo drive in DriveInfo.GetDrives())
            {
                if (drive.IsReady)
                {
                    DirectoryTreeViewItem d = new DirectoryTreeViewItem();
                    d.DriveName = SystemInfo.GetDriveClearName(drive.Name);
                    d.LabelName = drive.VolumeLabel == "" ? "本地磁盘" : drive.VolumeLabel;
                    if (SystemInfo.OSVersion() == "Win XP")
                    {
                        d.DiskIcon = SystemInfoIcon.GetDirectoryIcon(drive.Name, false).ToIcon2ImageSource();
                    }
                    else
                    {
                        d.DiskIcon = SystemInfoIcon.GetDirectoryIcon(d.DriveName, false).ToIcon2ImageSource();
                    }
                    d.DirTag = drive;
                    d.DriveType = "drive";
                    //判断是否有子目录
                    int HasDir = drive.RootDirectory.GetDirectories().Count();
                    if (HasDir > 0)
                    {
                        DirectoryTreeViewItem temp = new DirectoryTreeViewItem();
                        d.Children.Add(temp);
                    }
                    root.Children.Add(d);
                    #region 版本一 测试 已经废弃
                    //TreeViewItem item = new TreeViewItem();
                    //item.Tag = drive;
                    //item.Header = String.Format("{0}({1})", drive.VolumeLabel == "" ? "本地磁盘" : drive.VolumeLabel, GetChunName(drive.Name));
                    //item.Items.Add("*");
                    //tvDirectoryDemo.Items.Add(item); 

                    //imgIcon.Source = SystemInfoIcon.GetDirectoryIcon("c:", true).ToIcon2ImageSource();
                    #endregion
                }
            }
            dirTree.Add(root);

            tvDirectoryDemo.ItemsSource = dirTree;
            isExecuteExpand = true;
        }

这里,我增加了以下代码,是为了提示用户某文件夹下是有文件夹的,我们只是还没有加载而已。

   int HasDir = drive.RootDirectory.GetDirectories().Count();
                    if (HasDir > 0)
                    {
                        DirectoryTreeViewItem temp = new DirectoryTreeViewItem();
                        d.Children.Add(temp);
                    }

接着,我们添加展开事件

  bool isExecuteExpand=false;
        private void tvDirectoryDemo_Expanded(object sender, RoutedEventArgs e)
        {
            if (isExecuteExpand) {
                TreeViewItem item = (TreeViewItem)e.OriginalSource;
                item.IsSelected = true;
                DirectoryTreeViewItem temp = tvDirectoryDemo.SelectedItem as DirectoryTreeViewItem;
                item.IsSelected = false;
                if (temp != null)
                {
                    temp.Children.Clear();
                    DirectoryInfo dir;
                    if (temp.DirTag is DriveInfo)
                    {
                        DriveInfo drive = (DriveInfo)temp.DirTag;
                        dir = drive.RootDirectory;
                    }
                    else
                    {
                        dir = (DirectoryInfo)temp.DirTag;
                    }
                    try
                    {
                        DirectoryTreeViewItem tempItem = new DirectoryTreeViewItem();
                        foreach (DirectoryInfo subDir in dir.GetDirectories())
                        {
                            DirectoryTreeViewItem d = new DirectoryTreeViewItem();
                            d.DriveName = "";
                            d.LabelName = subDir.Name;
                            d.DirTag = subDir;
                            d.DiskIcon = SystemInfoIcon.GetDirectoryIcon(subDir.FullName, false).ToIcon2ImageSource();
                            d.DriveType = "dir";

                           // 判断是否有子目录
                            //int HasDir = subDir.GetDirectories().Count();
                            //if (HasDir > 0)
                            //{
                            //    DirectoryTreeViewItem tempItem = new DirectoryTreeViewItem();
                            //    d.Children.Add(tempItem);
                            //}

                            //var HasDirs = subDir.GetDirectories();
                            //if (HasDirs != null)
                            //{
                            //    int HasDir = HasDirs.Count();
                            //    if (HasDir > 0)
                            //    {
                            //        DirectoryTreeViewItem tempItem = new DirectoryTreeViewItem();
                                d.Children.Add(tempItem);
                            //    }
                            //}
                         

                            temp.Children.Add(d);
                        }
                    }
                    catch(Exception ex)
                    {
                        Console.WriteLine("出现错误");
                    }
                }
            }
        }

这里我增加了isExecuteExpand,是为了保证默认节点在加载的时候,expanded不触发,因为我们在后台添加驱动器的节点的时候,已经让我的电脑的IsExpanded展开了,所以会触发事件。

为了拿到DirectoryTreeViewItem对象,我临时使用了TreeView的SelectedItem,但是展开节点时候,是没有选中结点的,所有这个对象拿不到,但是我可以拿到触发事件的TreeVeiwItem,然后设置IsSelected=true,然后拿到对象,再置为false,如果你有好的办法你可以,可以评论给ay,ay感激不尽。

运行项目,效果图如下:

[Aaronyang] 写给自己的WPF4.5 笔记10[层次数据需求处理,TreeView绿色文章1/4]

静态显示

[Aaronyang] 写给自己的WPF4.5 笔记10[层次数据需求处理,TreeView绿色文章1/4]

但是以上如果增加判断是否有子文件夹,如果有,就增加一个空的DirectoryTreeViewItem对象,这样子,TreeViewItem就会增加一个小三角标注一下是否有子文件夹。但是有的文件夹我们无法访问,导致上面的C盘的文件夹获取的不完整

[Aaronyang] 写给自己的WPF4.5 笔记10[层次数据需求处理,TreeView绿色文章1/4]

如果不论三七二十一都增加一个空的DirectoryTreeViewItem,但是对用户是有错误引导的。我还没有找到解决办法,这应该是权限问题,但是我管理员运行了还没有找到解决办法。如果你直到怎么解决了,欢迎评论给我,我感激不尽。非常感谢。

[Aaronyang] 写给自己的WPF4.5 笔记10[层次数据需求处理,TreeView绿色文章1/4]理想的效果图,应该没有子文件夹的文件夹不显示三角形。

  =============潇洒的版权线==========www.ayjs.net===== Aaronyang ========= AY =========== 安徽 六安 杨洋 ==========   未经允许不许转载 =========

-------------------小小的推荐,作者的肯定,读者的支持。推不推荐不重要,重要的是希望大家能把WPF推广出去,别让这么好的技术消失了,求求了,让我们为WPF技术做一份贡献。------

 

[Aaronyang] 写给自己的WPF4.5 笔记10[层次数据需求处理,TreeView绿色文章1/4]

上一篇:WP8.1学习系列(第十七章)——Windows Phone重要图形、视觉指示器和通知


下一篇:ApiGen 4.0配置项