将winform treeview变成wpf treeview

我已经构建了一个在winforms中生成树视图的函数。 它包括带有递归的子文件夹和文件。 现在我想把它翻译成wpf。

我无法弄清楚如何处理这些类。 我知道我必须制作我自己的自定义类’treenode’,它具有类似于winforms treenode的属性’ parent ‘。

但是在wpf中我需要两种不同类型的treenode,所以我可以通过数据类型正确绑定wpf。 我在使用家庭的wpf中有一个工作示例,我只是不确定如何将我的winform版本转换为wpf。 有人可以帮助我让我的winform版本在wpf中运行吗?

然后最终目标是在WPF中使用目录和文件填充我的树视图,如我的winforms示例中所示。 但是,WPF版本的样式应该保持文件和文件夹的“图标”显示。

我希望有人可以帮助我正常工作。 欢迎任何建议和意见。

在此处输入图像描述


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 directoryNodes; public ObservableCollection DirectoryNodes { get { return directoryNodes; } set { directoryNodes = value; NotifyPropertyChanged("DirectoryNodes"); } } private ObservableCollection formats; public ObservableCollection Formats { get { return formats; } set { formats = value; NotifyPropertyChanged("Formats"); } } private ObservableCollection directories; public ObservableCollection Directories { get { return directories; } set { directories = value; NotifyPropertyChanged("Directories"); } } // Creating data for testings public ViewModel() { Formats = new ObservableCollection(); Directories = new ObservableCollection(); DirectoryNodes = new ObservableCollection(); // 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 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 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 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

           

工作Winforms示例

 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 formats = new List(); public Form1() { InitializeComponent(); //add userfolder List Directories = new List(); Directories.Add(System.Environment.GetEnvironmentVariable("USERPROFILE")); // get formats accepted formats.Add(".txt"); formats.Add(".png"); PopulateTree(Directories, formats); } static bool IsValidFileFormat(string filename, List 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 directories, List formats) { // main collection of nodes which are used to populate treeview List treeNodes = new List(); foreach (string directoryPath in directories) { if (Directory.Exists(directoryPath)) { DirectoryInfo directoryInfo = new DirectoryInfo(directoryPath); treeNodes.Add(CreateDirectoryNode(directoryInfo)); } } treeView1.Nodes.AddRange(treeNodes.ToArray()); } } } 

看看你的例子,我不确定到底发生了什么。 您可以查看输出,看看问题是否源于在运行时未找到的绑定。

但我会建议您将逻辑分开一些,将其中的一部分移到模型中。 我还建议您将模型隐藏在界面后面。 这允许您的视图模型包含单个集合,而视图根据类型呈现该集合的内容。 您当前的实现仅限于显示文件,作为目录的子项,而不是目录文件。 以下是适合您的工作示例。

模特

索引节点

创建INode接口将允许您为要呈现到Treeview的每个内容项创建不同的实现。

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

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

DirectoryNode

这是我们所有节点的根节点。 在大多数情况下,所有其他节点将通过父子关系与DirectoryNode相关联。 DirectoryNode将负责构建自己的子节点集合。 这会将逻辑移动到模型中,在模型中它可以validation自身并创建EmptyFolderNodes或根据需要生成FileNodes集合。 这会略微清理视图模型,因此它需要做的就是促进与视图本身的交互。

DirectoryNode将实现INotifyPropertyChange以便我们可以将属性更改事件提升到数据绑定到我们的任何内容。 这只会由这个模型上的Children属性。 其余属性将是只读的。

 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 children; public DirectoryNode(DirectoryInfo directoryInfo) { this.Directory = directoryInfo; this.Children = new ObservableCollection(); } public DirectoryNode(DirectoryInfo directoryInfo, DirectoryNode parent) : this(directoryInfo) { this.Parent = parent; } public event PropertyChangedEventHandler PropertyChanged; ///  /// Gets the name of the folder associated with this node. ///  public string Name { get { return this.Directory == null ? string.Empty : this.Directory.Name; } } ///  /// Gets the path to the directory associated with this node. ///  public string Path { get { return this.Directory == null ? string.Empty : this.Directory.FullName; } } ///  /// Gets the parent directory for this node. ///  public DirectoryNode Parent { get; private set; } ///  /// Gets the directory that this node represents. ///  public DirectoryInfo Directory { get; private set; } ///  /// Gets or sets the children nodes that this directory node can have. ///  public ObservableCollection Children { get { return this.children; } set { this.children = value; this.OnPropertyChanged(); } } ///  /// 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. ///  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(); 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(new List { new EmptyFolderNode(this) }); } else { // We fill our Children collection with the folder and file nodes we previously created above. this.Children = new ObservableCollection(childrenNodes); } } private void OnPropertyChanged([CallerMemberName] string propertyName = "") { var handler = this.PropertyChanged; if (handler == null) { return; } handler(this, new PropertyChangedEventArgs(propertyName)); } } } 

这里有几点需要注意。 一个是模型将始终被赋予对它表示为节点的DirectoryInfo的引用。 接下来,可以选择为其提供父DirectoryNode 。 这使我们可以轻松支持模型中的前向导航(通过Children属性)和向后导航(通过Parent属性)。 当我们将子DirectoryInfo项的集合转换为DirectoryInfo项的集合时,我们将自己传递给每个子DirectoryNode以便在需要时可以访问其父项。

Children系列是INode模型的集合。 这意味着DirectoryNode可以容纳各种不同类型的节点,并且可以轻松扩展以支持更多节点。 您只需要更新BuildChildrenNodes方法。

EmptyFolderNode

我们将实现的最简单的nodel是一个空文件夹节点。 如果双击文件夹,并且没有任何内容,我们将向用户显示一个节点,让他们知道它是空的。 此节点将具有预定义的Name ,并始终属于父目录。

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

这里没有太多进展,我们将名称指定为“空”并默认我们的父路径。

filenode的

我们需要构建的最后一个模型是FileNode 。 此节点表示层次结构中的文件,并且需要为其指定DirectoryNode 。 它还需要此节点表示的FileInfo

 using System.IO; namespace DirectoryTree { public class FileNode : INode { public FileNode(DirectoryNode parent, FileInfo file) { this.File = file; this.Parent = parent; } ///  /// Gets the parent of this node. ///  public DirectoryNode Parent { get; private set; } ///  /// Gets the file this node represents. ///  public FileInfo File { get; private set; } ///  /// Gets the filename for the file associated with this node. ///  public string Name { get { return this.File == null ? string.Empty : this.File.Name; } } ///  /// Gets the path to the file that this node represents. ///  public string Path { get { return this.File == null ? string.Empty : this.File.FullName; } } } } 

此时此模型的内容应该是不言自明的,所以我不会花时间在它上面。

视图模型

现在我们已经定义了模型,我们可以设置视图模型来与它们进行交互。 视图模型需要实现两个接口。 第一个是INotifyPropertyChanged以便我们可以向视图触发属性更改通知。 第二个是ICommand以便当需要加载更多目录或文件时,视图可以告诉视图模型。 我建议将ICommand东西抽象到可以重用的单个类中,或者使用像PrismMVVMLight这样的现有库,这两个库都有可以使用的命令对象。

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

视图模型具有RootNodes的集合。 这是视图将绑定到的INode实例的初始集合。 此初始集合将包含Program Files目录中的所有文件和文件夹。

当用户双击TreeViewItem中的TreeViewItem时, Execute方法将触发。 此方法将清除所选目录的子集合,或构建子集合。 这样,当用户折叠视图中的文件夹时,我们会自行清理并清空集合。 这也意味着在打开/关闭目录时,将始终刷新集合。

风景

这是最复杂的项目,但一看到它就相当简单。 就像您的示例一样,每个节点类型都有模板。 在我们的例子中,Treeview被数据绑定到我们的视图模型INode集合。 然后,我们为INode接口的每个实现提供了一个模板。

                                           

记录XAML代码以解释发生了什么,所以我不会添加它。

最终结果如下:

在此处输入图像描述

这应该可以得到你想要的。 如果没有,请告诉我。 如果你想要的只是一个目录 – >文件关系,那么你可以在构建它的Children集合时更新BuildChildrenNodes()方法以跳过目录查找。

最后要展示的是您现在在视图中的灵活性。 由于FileNode包含其父DirectoryNode及其表示的FileInfo因此您可以使用数据触发器有条件地更改在视图中显示内容的方式。 下面,我FileNode您展示FileNode数据模板上的两个数据触发器。 如果文件扩展名为.dll,则将TextBlock变为红色;如果扩展名为.exe,则将TextBlock变为蓝色。

            

最终结果如下:

在此处输入图像描述

您还可以在Execute方法中Execute条件逻辑,以不同方式处理每种不同类型的文件。 如果调用Execute方法,并且文件扩展名为.exe,而不是像我们现在那样忽略文件,则可以启动可执行文件。 此时你有很大的灵活性。