首页 > 代码库 > [Aaronyang] 写给自己的WPF4.5 笔记10[层次数据需求处理,TreeView绿色文章1/4]
[Aaronyang] 写给自己的WPF4.5 笔记10[层次数据需求处理,TreeView绿色文章1/4]
我的文章一定要做到对读者负责,否则就是失败的文章 --------- www.ayjs.net aaronyang技术分享
AY留言:
文章根据难易,我根据游戏的规则进行了分色,希望读者能选择自己的能力去读。白色<绿色<蓝色<紫色<橙色<红色
博文摘要:
- 简单的TreeView静态写法,了解展开事件,选中事件
- 关于磁盘驱动器的图标的获得,文件夹的图标的获得,文件的图标的获得,系统自己shell32.dll的图标的获得(例如我的电脑,回收站等icon)
- 关于TreeView的HierarchicalDataTemplate模板的讲解
- 实战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]