越来越WinForm的树状到WPF树视图 [英] getting winform treeview into wpf treeview

查看:702
本文介绍了越来越WinForm的树状到WPF树视图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经建立其生成的WinForms树形视图功能。它包括的子文件夹和文件递归。现在我要到WPF翻译这个。



我在遇到麻烦找出如何处理类。我知道我必须作出自己的自定义类树节点,这将有一个属性'的的'类似的WinForms树节点。



然而,在WPF我需要两种不同类型的树节点,所以我可以正确地绑定根据数据类型的WPF。我在使用WPF的familys工作的例子,我只是不知道如何让我的winform版本转换为WPF。谁能帮我把我的WinForm的版本在WPF中工作?



然后最终目标是让我的树视图在WPF中使用的目录和文件,看到我的WinForms例子来填充。但是的WPF版本的造型应保持图标显示文件和文件夹。



我希望有人能帮助我得到这个工作正常。任何的建议和意见都欢迎。



< IMG SRC =http://i.stack.imgur.com/PGHMw.pngALT =在这里输入的形象描述>






ViewModel.cs

 使用系统; 
使用System.Collections.Generic;
使用System.Collections.ObjectModel;
:使用System.IO;使用System.Windows
;
使用System.Windows.Input;
使用System.Linq的;

命名空间WpfApplication1
{
公共类视图模型:ObservableObject
{
//属性
私人的ObservableCollection< DirectoryNode> directoryNodes;
公众的ObservableCollection< DirectoryNode> DirectoryNodes
{
{返回directoryNodes; }

{
directoryNodes =价值;
NotifyPropertyChanged(DirectoryNodes);
}
}

私人的ObservableCollection<串GT;格式;
公众的ObservableCollection<串GT;格式
{
{返回格式; }

{
格式=价值;
NotifyPropertyChanged(格式);
}
}

私人的ObservableCollection<串GT;目录;
公众的ObservableCollection<串GT;目录
{
{返回目录; }

{
目录=价值;
NotifyPropertyChanged(目录);
}
}

//创建用于testings
数据公共视图模型()
{
格式=新的ObservableCollection<串>() ;
目录=新的ObservableCollection<串GT;();
DirectoryNodes =新的ObservableCollection< DirectoryNode>();

//创建一些虚拟的测试数据,最终将推动GUI
Formats.Add(名为txt);
Formats.Add(PNG);
Directories.Add(System.Environment.GetEnvironmentVariable(USERPROFILE));

PopulateTree(目录);
}

//功能
静态布尔IsValidFileFormat(字符串的文件名,的ObservableCollection<串>格式)
{
如果(formats.Count == 0 )返回true;

串EXT = Path.GetExtension(文件名);
布尔结果= formats.Any(的fileType => fileType.Equals(EXT,StringComparison.OrdinalIgnoreCase));
返回结果;
}

公共静态DirectoryNode CreateDirectoryNode(DirectoryInfo的DirectoryInfo的)
{
DirectoryNode directoryNode =新DirectoryNode(){文件名= directoryInfo.Name};

的foreach(在directoryInfo.GetDirectories var目录())
{

{
directoryNode.Children.Add(CreateDirectoryNode(目录));
}
赶上(UnauthorizedAccessException){}
}
的foreach(在directoryInfo.GetFiles var文件())
{
如果(IsValidFileFormat(文件。全名,格式))
{
filenode的节点=新filenode的(){文件名= file.FullName};
directoryNode.Children.Add(节点);
}
}

返回directoryNode;
}

公共无效PopulateTree(的ObservableCollection<串>的目录)
{
的foreach(目录中的字符串目录路径)
{
如果( Directory.Exists(目录))
{
的DirectoryInfo DirectoryInfo的=新的DirectoryInfo(目录);
DirectoryNodes.Add(CreateDirectoryNode(DirectoryInfo的));
}
}
}
}

公共类filenode的
{
公共字符串文件路径{搞定;组; }
公共字符串文件名{获得;组; }
公共DirectoryNode家长{搞定;组; }
}

公共类DirectoryNode
{
公共字符串文件路径{搞定;组; }
公共字符串文件名{获得;组; }
公共DirectoryNode家长{搞定;组; }
公众的ObservableCollection<&filenode的GT;儿童{搞定;组; }
}

公共类ObservableObject:INotifyPropertyChanged的
{
公共事件PropertyChangedEventHandler的PropertyChanged;

公共无效NotifyPropertyChanged([CallerMemberName]字符串参数propertyName = NULL)
{
PropertyChangedEventHandler处理器=的PropertyChanged;
如果(处理!= NULL)
{
处理器(这一点,新PropertyChangedEventArgs(propertyName的));
}
}
}
}

< STRONG> MainWindow.Xaml

 <窗口x:类=WpfApplication1.MainWindow
的xmlns =http://schemas.microsoft.com/winfx/2006/xaml/presentation
的xmlns:X =http://schemas.microsoft.com/winfx/2006/xaml
的xmlns :自=CLR的命名空间:WpfApplication1
标题=主窗口HEIGHT =350WIDTH =300
WindowStartupLocation =中心屏幕>

< Window.DataContext>
<自:视图模型/>
< /Window.DataContext>

<电网保证金=5>
<树视图的ItemsSource ={结合目录}Grid.Row =1Grid.ColumnSpan =2>
< TreeView.Resources>
< HierarchicalDataTemplate数据类型={X:型自:DirectoryNode}的ItemsSource ={结合儿童}>
< StackPanel的方向=横向>
<标签VerticalAlignment =中心的FontFamily =宋体CONTENT =1/>
< TextBlock的文本={结合文件名}/>
< / StackPanel的>
< / HierarchicalDataTemplate>
<数据类型的DataTemplate ={X:型自:filenode的}>
< StackPanel的方向=横向>
<标签VerticalAlignment =中心的FontFamily =宋体CONTENT =2/>
< TextBlock的文本={结合文件名}/>
< / StackPanel的>
< / DataTemplate中>
< /TreeView.Resources>
< / TreeView的>
< /网格和GT;

< /窗GT;






工作的WinForms示例

 使用系统; 
使用System.Collections.Generic;
使用System.Drawing中;
:使用System.IO;使用System.Windows.Forms的
;
使用System.Linq的;


命名空间WindowsFormsApplication1
{
公共部分Form1类:表格
{
公共静态列表<串GT;格式=新的List<串GT;();

公共Form1中()
{
的InitializeComponent();

//添加userfolder
名单,LT;字符串>目录=新的List<串GT;();
Directories.Add(System.Environment.GetEnvironmentVariable(USERPROFILE));

//获取格式接受
formats.Add(名为txt);
formats.Add(PNG);

PopulateTree(目录,格式);
}

静态布尔IsValidFileFormat(字符串的文件名,目录<串GT;格式)
{
如果(formats.Count == 0)返回true;

串EXT = Path.GetExtension(文件名);
布尔结果= formats.Any(的fileType => fileType.Equals(EXT,StringComparison.OrdinalIgnoreCase));
返回结果;
}

公共静态树节点CreateDirectoryNode(DirectoryInfo的DirectoryInfo的)
{
树节点directoryNode =新的TreeNode(directoryInfo.Name);

的foreach(在directoryInfo.GetDirectories var目录())
{

{
directoryNode.Nodes.Add(CreateDirectoryNode(目录));
}
赶上(UnauthorizedAccessException){}
}
的foreach(在directoryInfo.GetFiles var文件())
{
如果(IsValidFileFormat(文件。全名,格式))
{
树节点节点=新的TreeNode(file.FullName);
node.ForeColor = Color.Red;
directoryNode.Nodes.Add(节点);
}
}

返回directoryNode;
}

公共无效PopulateTree(列表<串>目录,清单和LT;串>格式)
{
//主节点,这是用来填充树视图集合
名单,LT;树节点>树节点=新的List<&树节点GT;();

的foreach(在目录字符串目录路径)
{
如果(Directory.Exists(目录))
{
的DirectoryInfo DirectoryInfo的=新的DirectoryInfo(目录) ;
treeNodes.Add(CreateDirectoryNode(DirectoryInfo的));
}
}

treeView1.Nodes.AddRange(treeNodes.ToArray());
}
}
}


解决方案

在你比如有看,我不知道到底发生了什么。你可以在你的输出看看,看看问题是否从没有在运行时发现绑定造成的。



不过,我会建议你分割逻辑好一点,将一些本到模型。我也建议你隐藏你的模型的接口后面。这使您的视图模型持有单个集合,而视图呈现基于类型的集合的内容。您当前的执行限制为只显示文件,因为孩子到一个目录,而不是目录的的文件。以下是为你工作的例子。



该机型



的inode



创建的inode 界面允许您创建要渲染到TreeView。每个内容项的不同实现

 命名空间DirectoryTree 
{
公共接口的inode
{
字符串名称{ ; }
路径字符串{搞定; }
}
}

我们的的inode 只需要两个属性。其中一个表示节点(通常是文件夹或文件名)的名称,一个代表的完整路径的文件夹或文件,它代表了。



DirectoryNode



这是我们所有节点的根节点。在大多数情况下,所有其他节点将要通过父子关系与 DirectoryNode 相关。在 DirectoryNode 将负责建立自己的子节点的集合。这个移动逻辑到模型,在那里它可以验证自身,并创建EmptyFolderNodes或者根据需要产生FileNodes的集合。这种清理视图模型一点,让所有需要做的是方便与视图本身的交互。



DirectoryNode 将实施 INotifyPropertyChange 这样我们就可以提高属性更改事件,以任何databinds给我们。这只有通过这个模型的孩子属性。 。属性的其余部分将是只读的。



 使用System.Collections.Generic; 
使用System.Collections.ObjectModel;
使用System.ComponentModel;
:使用System.IO;
使用System.Linq的;使用System.Runtime.CompilerServices
;

命名空间DirectoryTree
{
公共类DirectoryNode:inode,请INotifyPropertyChanged的
{
私人的ObservableCollection<&inode的GT;儿童;

公共DirectoryNode(DirectoryInfo的DirectoryInfo的)
{
this.Directory =的DirectoryInfo;
this.Children =新的ObservableCollection<&inode的GT;();
}

公共DirectoryNode(DirectoryInfo的DirectoryInfo的,DirectoryNode家长):这个(DirectoryInfo的)
{
this.Parent =父母;
}

公共事件PropertyChangedEventHandler的PropertyChanged;

///<总结>
///获取与此节点相关联的文件夹的名称。
///< /总结>
公共字符串名称
{
得到
{
返回this.Directory == NULL?的String.Empty:this.Directory.Name;
}
}

///<总结>
///获取与此节点关联的目录的路径。
///< /总结>
公共字符串路径
{
得到
{
返回this.Directory == NULL?的String.Empty:this.Directory.FullName;
}
}

///<总结>
///获取此节点的父目录。
///< /总结>
公共DirectoryNode家长{搞定;私人集; }

///<总结>
///获取此节点代表的目录。
///< /总结>
公共DirectoryInfo的目录{搞定;私人集; }

///<总结>
///获取或设置子节点,这个目录节点可以有。
///< /总结>
公众的ObservableCollection<&inode的GT;儿童
{
得到
{
返回this.children;
}


{
this.children =价值;
this.OnPropertyChanged();
}
}

///<总结>
///扫描当前目录下,并创建子节点的一个新的集合。
///子节点集合可填充EmptyFolderNode,filenode的或DirectoryNode实例。
///儿童系列将始终在其内至少有1个元素。
///< /总结>
公共无效BuildChildrenNodes()
{
//获取所有的文件夹和文件在我们的当前目录。的FileInfo [] = filesInDirectory this.Directory.GetFiles()
; DirectoryInfo的[] directoriesWithinDirectory = this.Directory.GetDirectories()
;

//将文件夹和文件到目录和文件的节点,并把它们添加到一个临时的集合。
变种childrenNodes =新的List<&inode的GT;();
childrenNodes.AddRange(directoriesWithinDirectory.Select(DIR =>新建DirectoryNode(DIR,这一点)));
childrenNodes.AddRange(filesInDirectory.Select(文件=>新建filenode的(这一点,文件)));

如果(childrenNodes.Count == 0)
{
//如果没有孩子的目录或文件,我们设置Children集合持有
//表示一个空目录的单个节点。
this.Children =新的ObservableCollection<&inode的GT(新的List<&inode的GT; {新EmptyFolderNode(本)});
}
,否则
{
//填写我们的孩子收集与我们先前上面创建的文件夹和文件的节点。
this.Children =新的ObservableCollection<&inode的GT;(childrenNodes);
}
}

私人无效OnPropertyChanged([CallerMemberName]字符串参数propertyName =)
{
VAR处理器= this.PropertyChanged;
如果(处理程序== NULL)
{
的回报;
}

处理器(这一点,新PropertyChangedEventArgs(propertyName的));
}
}
}



几件事情要注意这里。其中之一是,该模型将始终给予的DirectoryInfo 它是代表一个节点的引用。其次,还可以随意指定一个父 DirectoryNode 。这让我们很容易支持前进导航(通过孩子属性)和向后导航(通过属性)在我们的模型。当我们把孩子们的集合的DirectoryInfo 物品进入的 DirectoryNode 项目的集合,我们通过我们自己到每个孩子 DirectoryNode 使其能访问到其父如果需要的话。



孩子集合为的inode 模式的集合。这意味着 DirectoryNode 可举办各种不同类型的节点,并可以很容易地扩展,以支持更多。你只需要更新 BuildChildrenNodes 方法。



EmptyFolderNode



我们将实现最简单的结点1是一个空文件夹节点。如果您双击某个文件夹上,并没有任何内容,我们将显示给用户,让他们知道它是空的一个节点。该节点将有一个预先定义的名称,并将总是的属于父目录。

 命名空间DirectoryTree 
{
公共类EmptyFolderNode:inode的
{
公共EmptyFolderNode(DirectoryNode父)
{
this.Parent =父母;
this.Name =空;
}

公共字符串名称{;私人集; }

公共字符串路径
{
得到
{
返回this.Parent == NULL?的String.Empty:this.Parent.Path;
}
}

公共DirectoryNode家长{搞定;私人集; }
}
}

有没有多的事情在这里,我们指定名称为空,并默认了我们的父路径。



filenode的



我们需要建立的最后一个模式是 filenode的。该节点表示在我们的层次结构中的文件,并要求 DirectoryNode 给予它。它还要求的FileInfo 这个节点表示。



 使用系统。 IO; 

命名空间DirectoryTree
{
公共类filenode的:inode的
{
公共filenode的(DirectoryNode母公司的FileInfo文件)
{
this.File =文件;
this.Parent =父母;
}

///<总结>
///获取此节点的父节点。
///< /总结>
公共DirectoryNode家长{搞定;私人集; }

///<总结>
///获取此节点代表文件。
///< /总结>
公众的FileInfo文件{搞定;私人集; }

///<总结>
///获取与该节点相关联的文件的文件名。
///< /总结>
公共字符串名称
{
得到
{
返回this.File == NULL?的String.Empty:this.File.Name;
}
}

///<总结>
///获取的路径,此节点代表了文件。
///< /总结>
公共字符串路径
{
得到
{
返回this.File == NULL?的String.Empty:this.File.FullName;
}
}
}
}



这种模式在这一点上的内容应该是相当不言自明的,所以我不会花任何时间就可以了。



视图模式



现在,我们有我们的模型定义,我们可以设置视图模型与他们进行互动。视图模型需要实现两个接口。首先是 INotifyPropertyChanged的这样我们就可以火属性更改通知视图。第二个是的ICommand 这样的观点可以告诉视图模型时,多个目录或文件需要加载。我建议抽象的ICommand 的东西了成可重复使用一个单独的类,或者使用类似棱镜现有的库 MVVMLight ,这两者都可以用居高临下的对象。

 使用系统; 
使用System.Collections.Generic;
使用System.ComponentModel;
:使用System.IO;使用System.Runtime.CompilerServices
;
使用System.Windows.Input;

命名空间DirectoryTree
{
公共类MainWindowViewModel:INotifyPropertyChanged的,ICommand的
{
私人的IEnumerable<&inode的GT; rootNodes;

私人的inode selectedNode;

公共MainWindowViewModel()
{
//我们默认应用程序文件目录的根目录。
串programFilesPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);

//我们的转换程序文件路径字符串转换成DirectoryInfo的,创造我们的初步DirectoryNode。
变种rootDirectoryInfo =新DirectoryInfo的(programFilesPath);
无功根目录=新DirectoryNode(rootDirectoryInfo);

//告诉我们的根节点建立它的孩子们收藏。
rootDirectory.BuildChildrenNodes();

this.RootNodes = rootDirectory.Children;
}

公共事件PropertyChangedEventHandler的PropertyChanged;
公共事件的EventHandler CanExecuteChanged;

公开的IEnumerable<&inode的GT; RootNodes
{
得到
{
返回this.rootNodes;
}


{
this.rootNodes =价值;
this.OnPropertyChanged();
}
}

公共BOOL CanExecute(对象参数)
{
//只有当我们都获得了选择的项目执行我们的指令。
返回参数!= NULL;
}

公共无效执行(对象参数)
{
//尝试转换为目录节点。如果返回null,则我们
//无论是filenode的或EmptyFolderNode。两者都不需要我们进行反应。
DirectoryNode currentDirectory所=参数作为DirectoryNode;
如果(currentDirectory所== NULL)
{
的回报;
}

//如果当前目录下有孩子,那么该视图崩溃了。
//在这种情况下,我们清除了带孩子出去,所以我们不逐步
//消耗系统资源,绝不放过。
如果(currentDirectory.Children.Count大于0)
{
currentDirectory.Children.Clear();
的回报;
}

//如果当前目录没有孩子,那么我们建立一个集合。
currentDirectory.BuildChildrenNodes();
}

私人无效OnPropertyChanged([CallerMemberName]字符串参数propertyName =)
{
VAR处理器= this.PropertyChanged;
如果(处理程序== NULL)
{
的回报;
}

处理器(这一点,新PropertyChangedEventArgs(propertyName的));
}
}
}



视图模型具有收藏 RootNodes的。这是视图将绑定到的inode 情况的初步收集。这个初始集合将包含的所有文件和文件夹的程序文件的目录。



在上<$ C $用户双击C>树型视图在视图中,执行方法将火熄灭。这个方法要么清除所选目录的孩子集合,或建立孩子的集合。这样,当用户在视图中倒塌的文件夹,我们清理后自己并清空集合。这也意味着,因为他们打开/关闭目录集合将总是被刷新。





该是最复杂的项目,但是一旦你看它这是相当简单的。就像你的榜样,有每个节点类型的模板。在我们的例子中,TreeView的是数据绑定到我们的视图模型的inode 集合。然后,我们havea模板的的inode 接口的每个实现。

 < ;窗口x:类=DirectoryTree.MainWindow
的xmlns =http://schemas.microsoft.com/winfx/2006/xaml/presentation
的xmlns:X =HTTP://模式.microsoft.com / WinFX的/ 2006 / XAML
的xmlns:地方=CLR的命名空间:DirectoryTree
的xmlns:I =CLR的命名空间:System.Windows.Interactivity;装配= System.Windows .Interactivity
标题=主窗口HEIGHT =350WIDTH =525>

<! - 分配视图模型到窗口。 - >
< Window.DataContext>
<局部:MainWindowViewModel />
< /Window.DataContext>

<&DockPanel中GT;
< TreeView的X:名称=FileExplorerTreeview
的ItemsSource ={绑定路径= RootNodes}>

<! - 我们使用的交互触发MouseDoubleClick事件映射到我们的视图模型。
由于视图模型实现ICommand的,我们只要直接绑定到视图模型。

这就要求你添加System.Windows.Interactivity.dll装配到您的项目引用。命名空间到你的XAML窗口,如上图所示..
- >:
还必须添加i
< I:Interaction.Triggers>
< I:的EventTrigger事件名称=MouseDoubleClick>
<! - 当一个文件夹的用户双击,我们将所选项目送入视图模型执行方法作为参数。然后
视图模型可以反应用是否这是一个DirectoryNode或filenode的。
- >
< I:InvokeCommandAction命令={结合}CommandParameter ={绑定的ElementName = FileExplorerTreeview,路径=的SelectedItem}/>
< /我:&的EventTrigger GT;
< /我:Interaction.Triggers>

< TreeView.Resources>
<! - 此模板表示DirectoryNode。该模板databinds自己的DirectoryNode
儿童属性,以便根据需要我们可以有嵌套的文件夹和文件。
- >
< HierarchicalDataTemplate数据类型={X:类型本地:DirectoryNode}
的ItemsSource ={绑定路径=儿童}>
< StackPanel的方向=横向>
将;标签的内容为1
的FontFamily =宋体
粗细=黑/>
<! - 需要更换W / A文件夹中的形象 - >
< TextBlock的文本={绑定路径=名称}/>
< / StackPanel的>
< / HierarchicalDataTemplate>

<! - 此模板表示filenode的。由于FileNodes不能有孩子,我们使这个标准,平,数据模板。 - >
<数据类型的DataTemplate ={X:类型本地:filenode的}>
< StackPanel的方向=横向>
<标签内容=2
的FontFamily =宋体
粗细=黑/>
<! - 需要更换W / A文件的图像 - >
< TextBlock的文本={绑定路径=路径}/>
< / StackPanel的>
< / DataTemplate中>

<! - 此模板表示EmptyFolderNode。由于EmptyFolderNode不能有子女或兄弟姐妹,我们使这个标准,平,数据模板。 - >
<数据类型的DataTemplate ={X:类型本地:EmptyFolderNode}>
< StackPanel的方向=横向>
<! - 需要更换W / A文件的图像 - >
< TextBlock的文本={绑定路径=名称}
字号=10
fontstyle的=斜体/>
< / StackPanel的>
< / DataTemplate中>
< /TreeView.Resources>
< / TreeView的>
< / DockPanel中>
< /窗GT;



该XAML代码被记录来解释所发生的事情,所以我不会补充。



最后的结果是这样的:





这应该得到你想要的东西。让我知道,如果它没有。如果你想要的是一个单一的目录 - >文件的关系,那么你可以构建时更新 BuildChildrenNodes()方法跳过目录查找它的儿童集合。



显示最后一件事是你现在在视图中的灵活性。因为 filenode的包含其父 DirectoryNode 的FileInfo 它代表,您可以使用数据触发条件的改变你如何在视图中显示的内容。下面,我给你上的 filenode的数据模板两种数据的触发器。一,轮流TextBlock的红色如果文件扩展名是.dll和另一个转动TextBlock的兰若扩展名是.exe文件。

 <数据类型的DataTemplate ={X:类型本地:filenode的}> 
< StackPanel的方向=横向>
<标签内容=2
的FontFamily =宋体
粗细=黑/>
<! - 需要更换W / A文件的图像 - >
< TextBlock的文本={绑定路径=路径}>
< TextBlock.Style>
<风格的TargetType =TextBlock的>
< Style.Triggers>
< DataTrigger绑定={绑定路径= File.Extension}
值=>执行。
< setter属性=前景
值=蓝/>
< / DataTrigger>

< DataTrigger绑定={绑定路径= File.Extension}
值=>DLL。
< setter属性=前景
值=红楼梦/>
< / DataTrigger>
< /Style.Triggers>
< /样式和GT;
< /TextBlock.Style>
< / TextBlock的>
< / StackPanel的>
< / DataTemplate中>



最后的结果是这样的:





You can also do conditional logic within the Execute method to handle each different type of file differently. If the Execute method is invoked, and the file extension is .exe, instead of ignoring the file like we are now, you could start the executable. You have a lot of flexibility at this point.


I've build a function which generates a treeview in winforms. It includes subfolders and files with recursion. Now i want to translate this over to wpf.

I'm having trouble figuring out how to handle the classes. I know i have to make my own custom class 'treenode' which would have a property 'parent' similar to the winforms treenode.

However in wpf I need two different types of treenodes so i can properly bind the wpf by data type. I have a working example in wpf using familys, I'm just not sure how to get my winform version translated to wpf. Can someone help me get my winform version working in wpf?

Then end goal is getting my treeview in WPF to populate using a directories and files as seen in my winforms example. However the styling of the WPF version should maintain the 'icons' displaying for files and folders.

i hope someone can help me get this working properly. Any suggestions and comments are welcome.


ViewModel.cs

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Windows;
using System.Windows.Input;
using System.Linq;

namespace WpfApplication1
{
    public class ViewModel : ObservableObject
    {
        // Properties
        private ObservableCollection<DirectoryNode> directoryNodes;
        public ObservableCollection<DirectoryNode> DirectoryNodes
        {
            get { return directoryNodes; }
            set
            {
                directoryNodes = value;
                NotifyPropertyChanged("DirectoryNodes");
            }
        }

        private ObservableCollection<string> formats;
        public ObservableCollection<string> Formats
        {
            get { return formats; }
            set
            {
                formats = value;
                NotifyPropertyChanged("Formats");
            }
        }

        private ObservableCollection<string> directories;
        public ObservableCollection<string> Directories
        {
            get { return directories; }
            set
            {
                directories = value;
                NotifyPropertyChanged("Directories");
            }
        }

        // Creating data for testings
        public ViewModel()
        {
            Formats = new ObservableCollection<string>();
            Directories = new ObservableCollection<string>();
            DirectoryNodes = new ObservableCollection<DirectoryNode>();

            // create some dummy test data, eventually will be push to GUI
            Formats.Add(".txt");
            Formats.Add(".png");
            Directories.Add(System.Environment.GetEnvironmentVariable("USERPROFILE"));

            PopulateTree(Directories);
        }

        // Functions
        static bool IsValidFileFormat(string filename, ObservableCollection<string> formats)
        {
            if (formats.Count == 0) return true;

            string ext = Path.GetExtension(filename);
            bool results = formats.Any(fileType => fileType.Equals(ext, StringComparison.OrdinalIgnoreCase));
            return results;
        }

        public static DirectoryNode CreateDirectoryNode(DirectoryInfo directoryInfo)
        {
            DirectoryNode directoryNode = new DirectoryNode(){Filename=directoryInfo.Name};

            foreach (var directory in directoryInfo.GetDirectories())
            {
                try
                {
                    directoryNode.Children.Add(CreateDirectoryNode(directory));
                }
                catch (UnauthorizedAccessException) { }
            }
            foreach (var file in directoryInfo.GetFiles())
            {
                if (IsValidFileFormat(file.FullName, Formats))
                {
                    FileNode node = new FileNode() { Filename = file.FullName };
                    directoryNode.Children.Add(node);
                }
            }

            return directoryNode;
        }

        public void PopulateTree(ObservableCollection<string> directories)
        {
            foreach (string directoryPath in directories)
            {
                if (Directory.Exists(directoryPath))
                {
                    DirectoryInfo directoryInfo = new DirectoryInfo(directoryPath);
                    DirectoryNodes.Add(CreateDirectoryNode(directoryInfo));
                }
            }
        }
    }

    public class FileNode
    {
        public string Filepath { get; set; }
        public string Filename { get; set; }
        public DirectoryNode Parent { get; set; }
    }

    public class DirectoryNode
    {
        public string Filepath { get; set; }
        public string Filename { get; set; }
        public DirectoryNode Parent { get; set; }
        public ObservableCollection<FileNode> Children { get; set; }
    }

    public class ObservableObject : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

MainWindow.Xaml

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:self="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="300"
        WindowStartupLocation="CenterScreen">

    <Window.DataContext>
        <self:ViewModel/>
    </Window.DataContext>

    <Grid Margin="5">        
        <TreeView ItemsSource="{Binding Directories}" Grid.Row="1" Grid.ColumnSpan="2">
            <TreeView.Resources>
                <HierarchicalDataTemplate DataType="{x:Type self:DirectoryNode}" ItemsSource="{Binding Children}">
                    <StackPanel Orientation="Horizontal">
                        <Label VerticalAlignment="Center" FontFamily="WingDings" Content="1"/>
                        <TextBlock Text="{Binding Filename}" />
                    </StackPanel>
                </HierarchicalDataTemplate>
                <DataTemplate DataType="{x:Type self:FileNode}">
                    <StackPanel Orientation="Horizontal">
                        <Label VerticalAlignment="Center" FontFamily="WingDings" Content="2"/>
                        <TextBlock Text="{Binding Filename}" />
                    </StackPanel>
                </DataTemplate>
            </TreeView.Resources>
        </TreeView>
    </Grid>

</Window>


Working Winforms Example

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using System.Linq;


namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public static List<string> formats = new List<string>();

        public Form1()
        {
            InitializeComponent();

            //add userfolder
            List<string> Directories = new List<string>();
            Directories.Add(System.Environment.GetEnvironmentVariable("USERPROFILE"));

            // get formats accepted
            formats.Add(".txt");
            formats.Add(".png");

            PopulateTree(Directories, formats);
        }

        static bool IsValidFileFormat(string filename, List<string> formats)
        {
            if (formats.Count == 0) return true;

            string ext = Path.GetExtension(filename);
            bool results = formats.Any(fileType => fileType.Equals(ext, StringComparison.OrdinalIgnoreCase));
            return results;
        }

        public static TreeNode CreateDirectoryNode(DirectoryInfo directoryInfo)
        {
            TreeNode directoryNode = new TreeNode(directoryInfo.Name);

            foreach (var directory in directoryInfo.GetDirectories())
            {
                try
                {
                    directoryNode.Nodes.Add(CreateDirectoryNode(directory));
                }
                catch (UnauthorizedAccessException) { }
            }
            foreach (var file in directoryInfo.GetFiles())
            {
                if (IsValidFileFormat(file.FullName, formats))
                {
                    TreeNode node = new TreeNode(file.FullName);
                    node.ForeColor = Color.Red;
                    directoryNode.Nodes.Add(node);
                }
            }

            return directoryNode;
        }

        public void PopulateTree(List<string> directories, List<string> formats)
        {
            // main collection of nodes which are used to populate treeview
            List<TreeNode> treeNodes = new List<TreeNode>();

            foreach (string directoryPath in directories)
            {
                if (Directory.Exists(directoryPath))
                {
                    DirectoryInfo directoryInfo = new DirectoryInfo(directoryPath);
                    treeNodes.Add(CreateDirectoryNode(directoryInfo));
                }
            }

            treeView1.Nodes.AddRange(treeNodes.ToArray());
        }
    }
}

解决方案

Looking at your example there, I'm not sure exactly what is happening. You can take a look in your output and see if the issue stems from bindings not being found at runtime.

I would recommend however that you split the logic up a bit more, moving some of this into your model. I would also recommend that you hide your models behind an interface. This allows your view model to hold a single collection, while the view renders the contents of that collection based on the type. Your current implementation is limited to only showing files, as children to a directory, instead of directories and files. Below is a working example for you.

The models

INode

Creating an INode interface will allow you to create different implementations of each content item you want to render to the Treeview.

namespace DirectoryTree
{
    public interface INode
    {
        string Name { get; }
        string Path { get; }
    }
}

Our INode only needs two properties. One that represents the name of the node (typically the folder or file name) and one that represents the full path to the folder or file it represents.

DirectoryNode

This is the root node for all of our nodes. In most cases, all other nodes are going to be associated with a DirectoryNode via a parent-child relationship. The DirectoryNode will be responsible for building its own collection of children nodes. This moves the logic into the model, where it can validate itself and create EmptyFolderNodes or generate a collection of FileNodes as needed. This cleans up the view model a bit, so that all it needs to do is facilitate the interactions with the view itself.

The DirectoryNode will implement INotifyPropertyChange so that we can raise property changed events to anything that databinds to us. This will only by the Children property on this model. The rest of the properties will be read-only.

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;

namespace DirectoryTree
{
    public class DirectoryNode : INode, INotifyPropertyChanged
    {
        private ObservableCollection<INode> children;

        public DirectoryNode(DirectoryInfo directoryInfo)
        {
            this.Directory = directoryInfo;
            this.Children = new ObservableCollection<INode>();
        }

        public DirectoryNode(DirectoryInfo directoryInfo, DirectoryNode parent) : this(directoryInfo)
        {
            this.Parent = parent;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Gets the name of the folder associated with this node.
        /// </summary>
        public string Name
        {
            get
            {
                return this.Directory == null ? string.Empty : this.Directory.Name;
            }
        }

        /// <summary>
        /// Gets the path to the directory associated with this node.
        /// </summary>
        public string Path
        {
            get
            {
                return this.Directory == null ? string.Empty : this.Directory.FullName;
            }
        }

        /// <summary>
        /// Gets the parent directory for this node.
        /// </summary>
        public DirectoryNode Parent { get; private set; }

        /// <summary>
        /// Gets the directory that this node represents.
        /// </summary>
        public DirectoryInfo Directory { get; private set; }

        /// <summary>
        /// Gets or sets the children nodes that this directory node can have.
        /// </summary>
        public ObservableCollection<INode> Children
        {
            get
            {
                return this.children;
            }

            set
            {
                this.children = value;
                this.OnPropertyChanged();
            }
        }

        /// <summary>
        /// Scans the current directory and creates a new collection of children nodes.
        /// The Children nodes collection can be filled with EmptyFolderNode, FileNode or DirectoryNode instances.
        /// The Children collection will always have at least 1 element within it.
        /// </summary>
        public void BuildChildrenNodes()
        {
            // Get all of the folders and files in our current directory.
            FileInfo[] filesInDirectory = this.Directory.GetFiles();
            DirectoryInfo[] directoriesWithinDirectory = this.Directory.GetDirectories();

            // Convert the folders and files into Directory and File nodes and add them to a temporary collection.
            var childrenNodes = new List<INode>();
            childrenNodes.AddRange(directoriesWithinDirectory.Select(dir => new DirectoryNode(dir, this)));
            childrenNodes.AddRange(filesInDirectory.Select(file => new FileNode(this, file)));

            if (childrenNodes.Count == 0)
            {
                // If there are no children directories or files, we setup the Children collection to hold
                // a single node that represents an empty directory.
                this.Children = new ObservableCollection<INode>(new List<INode> { new EmptyFolderNode(this) });
            }
            else
            {
                // We fill our Children collection with the folder and file nodes we previously created above.
                this.Children = new ObservableCollection<INode>(childrenNodes);
            }
        }

        private void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            var handler = this.PropertyChanged;
            if (handler == null)
            {
                return;
            }

            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Few things to note here. One is that the model will always be given a reference to the DirectoryInfo it is representing as a node. Next, it can optionally be given a parent DirectoryNode. This lets us easily support forward navigation (via the Children property) and backward navigation (via the Parent property) in our model. When we convert the collection of children DirectoryInfo items into a collection of DirectoryNode items, we pass ourself into each children DirectoryNode so it has access to its parent if needed.

The Children collection is a collection of INode models. This means the DirectoryNode can hold various different kinds of nodes and can easily be extended to support more. You just need to update the BuildChildrenNodes method.

EmptyFolderNode

The easiest nodel we will implement is an empty folder node. If you double click on a folder, and there isn't any contents, we will display a node to the user letting them know it's empty. This node will have a predefined Name, and will always belong to a parent directory.

namespace DirectoryTree
{
    public class EmptyFolderNode : INode
    {
        public EmptyFolderNode(DirectoryNode parent)
        {
            this.Parent = parent;
            this.Name = "Empty.";
        }

        public string Name { get; private set; }

        public string Path
        {
            get
            {
                return this.Parent == null ? string.Empty : this.Parent.Path;
            }
        }

        public DirectoryNode Parent { get; private set; }
    }
}

There isn't much going on here, we assign the name as "Empty" and default our path to the parent.

FileNode

The last model we need to build is the FileNode. This node represents a file in our hierarchy and requires a DirectoryNode be given to it. It also requires the FileInfo that this node represents.

using System.IO;

namespace DirectoryTree
{
    public class FileNode : INode
    {
        public FileNode(DirectoryNode parent, FileInfo file)
        {
            this.File = file;
            this.Parent = parent;
        }

        /// <summary>
        /// Gets the parent of this node.
        /// </summary>
        public DirectoryNode Parent { get; private set; }

        /// <summary>
        /// Gets the file this node represents.
        /// </summary>
        public FileInfo File { get; private set; }

        /// <summary>
        /// Gets the filename for the file associated with this node.
        /// </summary>
        public string Name
        {
            get
            {
                return this.File == null ? string.Empty : this.File.Name;
            }
        }

        /// <summary>
        /// Gets the path to the file that this node represents.
        /// </summary>
        public string Path
        {
            get
            {
                return this.File == null ? string.Empty : this.File.FullName;
            }
        }
    }
}

The contents of this model at this point should be pretty self-explanatory, so i won't spend any time on it.

The view model

Now that we have our models defined, we can set up the view model to interact with them. The view model needs to implement two interfaces. The first being INotifyPropertyChanged so that we can fire property changed notifications to the view. The second is ICommand so that the view can tell the view model when more directories or files need to be loaded. I recommend abstracting the ICommand stuff out into an individual class that can be reused, or using an existing library like Prism or MVVMLight, both of which have commanding objects you can use.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;
using System.Windows.Input;

namespace DirectoryTree
{
    public class MainWindowViewModel : INotifyPropertyChanged, ICommand
    {
        private IEnumerable<INode> rootNodes;

        private INode selectedNode;

        public MainWindowViewModel()
        {
            // We default the app to the Program Files directory as the root.
            string programFilesPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);

            // Convert our Program Files path string into a DirectoryInfo, and create our initial DirectoryNode.
            var rootDirectoryInfo = new DirectoryInfo(programFilesPath);
            var rootDirectory = new DirectoryNode(rootDirectoryInfo);

            // Tell our root node to build it's children collection.
            rootDirectory.BuildChildrenNodes();

            this.RootNodes = rootDirectory.Children;
        }

        public event PropertyChangedEventHandler PropertyChanged;
        public event EventHandler CanExecuteChanged;

        public IEnumerable<INode> RootNodes
        {
            get
            {
                return this.rootNodes;
            }

            set
            {
                this.rootNodes = value;
                this.OnPropertyChanged();
            }
        }

        public bool CanExecute(object parameter)
        {
            // Only execute our command if we are given a selected item.
            return parameter != null;
        }

        public void Execute(object parameter)
        {
            // Try to cast to a directory node. If it returns null then we are
            // either a FileNode or an EmptyFolderNode. Neither of which we need to react to.
            DirectoryNode currentDirectory = parameter as DirectoryNode;
            if (currentDirectory == null)
            {
                return;
            }

            // If the current directory has children, then the view is collapsing it.
            // In this scenario, we clear the children out so we don't progressively
            // consume system resources and never let go.
            if (currentDirectory.Children.Count > 0)
            {
                currentDirectory.Children.Clear();
                return;
            }

            // If the current directory does not have children, then we build that collection.
            currentDirectory.BuildChildrenNodes();
        }

        private void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            var handler = this.PropertyChanged;
            if (handler == null)
            {
                return;
            }

            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

The view model has a collection of RootNodes. This is the initial collection of INode instances the view will bind to. This initial collection will contain all of the files and folders within the Program Files directory.

When the user double clicks on a TreeViewItem in the view, the Execute method will fire off. This method will either clear the children collection of the selected directory, or build the collection of children. This way, as the user collapses folders in the view, we clean up after ourselves and empty the collection. This also means that the collection will always be refreshed as they open/close the directories.

The View

The is the most complex item, but it's fairly simple once you look at it. Just like your example, there are templates for each node type. In our case, the Treeview is databound to our view models INode collection. We then havea template for each implementation of the INode interface.

<Window x:Class="DirectoryTree.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DirectoryTree"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        Title="MainWindow" Height="350" Width="525">

    <!-- Assign a view model to the window. -->
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>

    <DockPanel>
        <TreeView x:Name="FileExplorerTreeview" 
                  ItemsSource="{Binding Path=RootNodes}">

            <!--    We use an interaction trigger to map the MouseDoubleClick event onto our view model.
                    Since the view model implements ICommand, we can just bind directly to the view model. 

                    This requires that you add the System.Windows.Interactivity.dll assembly to your project references.
                    You also must add the i: namespace to your XAML window, as shown above..
            -->
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="MouseDoubleClick">
                    <!--    When the user double clicks on a folder, we will send the selected item into the view models Execute method as a argument.
                            The view model can then react to wether or not it's a DirectoryNode or a FileNode.
                    -->
                    <i:InvokeCommandAction Command="{Binding }" CommandParameter="{Binding ElementName=FileExplorerTreeview, Path=SelectedItem}" />
                </i:EventTrigger>
            </i:Interaction.Triggers>

            <TreeView.Resources>
                <!--    This template represents a DirectoryNode. This template databinds itself to the Children property on the DirectoryNode
                        so we can have nested folders and files as needed. 
                -->
                <HierarchicalDataTemplate DataType="{x:Type local:DirectoryNode}"
                                          ItemsSource="{Binding Path=Children}">
                    <StackPanel Orientation="Horizontal">
                        <Label Content="1"
                               FontFamily="WingDings"
                               FontWeight="Black" />
                        <!-- Need to replace w/ an image of a folder -->
                        <TextBlock Text="{Binding Path=Name}" />
                    </StackPanel>
                </HierarchicalDataTemplate>

                <!-- This template represents a FileNode. Since FileNodes can't have children, we make this a standard, flat, data template. -->
                <DataTemplate DataType="{x:Type local:FileNode}">
                    <StackPanel Orientation="Horizontal">
                        <Label Content="2"
                               FontFamily="WingDings"
                               FontWeight="Black" />
                        <!-- Need to replace w/ an image of a file -->
                        <TextBlock Text="{Binding Path=Path}" />
                    </StackPanel>
                </DataTemplate>

                <!-- This template represents an EmptyFolderNode. Since EmptyFolderNode can't have children or siblings, we make this a standard, flat, data template. -->
                <DataTemplate DataType="{x:Type local:EmptyFolderNode}">
                    <StackPanel Orientation="Horizontal">
                        <!-- Need to replace w/ an image of a file -->
                        <TextBlock Text="{Binding Path=Name}" 
                                   FontSize="10"
                                   FontStyle="Italic"/>
                    </StackPanel>
                </DataTemplate>
            </TreeView.Resources>
        </TreeView>
    </DockPanel>
</Window>

The XAML code is documented to explain what's happening, so i'll not add to that.

The end result looks like this:

This should get you what you want. Let me know if it doesn't. If all you want is a single Directory->File relationship, then you can just update the BuildChildrenNodes() method to skip the Directory lookup when building it's Children collection.

One last thing to show is the flexibility you now have in the view. Since the FileNode contains its parent DirectoryNode and the FileInfo it represents, you can use data triggers to conditionally change how you display content in the view. Below, I show you two data-triggers on the FileNode data template. One that turns the TextBlock to red if the file extension is .dll, and another that turns the TextBlock blue if the extension is .exe.

<DataTemplate DataType="{x:Type local:FileNode}">
    <StackPanel Orientation="Horizontal">
        <Label Content="2"
                FontFamily="WingDings"
                FontWeight="Black" />
        <!-- Need to replace w/ an image of a file -->
    <TextBlock Text="{Binding Path=Path}">
        <TextBlock.Style>
            <Style TargetType="TextBlock">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Path=File.Extension}"
                                    Value=".exe">
                        <Setter Property="Foreground"
                                Value="Blue" />
                    </DataTrigger>

                    <DataTrigger Binding="{Binding Path=File.Extension}"
                                    Value=".dll">
                        <Setter Property="Foreground"
                                Value="Red" />
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </TextBlock.Style>
    </TextBlock>
    </StackPanel>
</DataTemplate>

The end result looks like this:

You can also do conditional logic within the Execute method to handle each different type of file differently. If the Execute method is invoked, and the file extension is .exe, instead of ignoring the file like we are now, you could start the executable. You have a lot of flexibility at this point.

这篇关于越来越WinForm的树状到WPF树视图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆