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

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

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

AY留言:

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

博文摘要:

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

       技术分享

       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显示文字

  技术分享

展开事件(展开时候,我们在它下面增加一个"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++;        }

技术分享

增加单击事件,满足菜单数导航的需求,我们可以把想要的参数放到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就弹出来,没有就不弹窗

技术分享

 

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>

效果图:

技术分享

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

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

后台创建数据并绑定ItemsSource

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

技术分享

 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);        }

效果图:

技术分享

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

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

技术分享
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() ;

运行后显示图片技术分享

这就说明我们在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,那么窗口中的,只要是这个数据对象,就会以这个数据模板展示

技术分享

那么如果有多个实体对象,例如 目录和产品, 部门和员工等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感激不尽。

运行项目,效果图如下:

技术分享

静态显示

技术分享

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

技术分享

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

技术分享理想的效果图,应该没有子文件夹的文件夹不显示三角形。

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

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

 

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