Populate Treeview with File System Directory Structure

Populate TreeView with file system directory structure

Option #1: Recursive approach:

private void ListDirectory(TreeView treeView, string path)
{
treeView.Nodes.Clear();
var rootDirectoryInfo = new DirectoryInfo(path);
treeView.Nodes.Add(CreateDirectoryNode(rootDirectoryInfo));
}

private static TreeNode CreateDirectoryNode(DirectoryInfo directoryInfo)
{
var directoryNode = new TreeNode(directoryInfo.Name);
foreach (var directory in directoryInfo.GetDirectories())
directoryNode.Nodes.Add(CreateDirectoryNode(directory));
foreach (var file in directoryInfo.GetFiles())
directoryNode.Nodes.Add(new TreeNode(file.Name));
return directoryNode;
}

Option #2: Non-recursive approach:

private static void ListDirectory(TreeView treeView, string path)
{
treeView.Nodes.Clear();

var stack = new Stack<TreeNode>();
var rootDirectory = new DirectoryInfo(path);
var node = new TreeNode(rootDirectory.Name) { Tag = rootDirectory };
stack.Push(node);

while (stack.Count > 0)
{
var currentNode = stack.Pop();
var directoryInfo = (DirectoryInfo)currentNode.Tag;
foreach (var directory in directoryInfo.GetDirectories())
{
var childDirectoryNode = new TreeNode(directory.Name) { Tag = directory };
currentNode.Nodes.Add(childDirectoryNode);
stack.Push(childDirectoryNode);
}
foreach (var file in directoryInfo.GetFiles())
currentNode.Nodes.Add(new TreeNode(file.Name));
}

treeView.Nodes.Add(node);
}

Populate TreeView with files and directories but without hidden folders and files

If you've based your code on that post, you could use the DirectoryInfo.Attributes property to check for Hidden or System values, for example, so you could let those items out of your TreeView list.

populate treeview from list of file paths in wpf

I was intrigued by the question and threw this together. As a first pass I think I'm pretty close to what you're looking for. Talking about 50,000 items though makes me think that lazy loading may be appropriate. Anyway, here is the simple version based on an article by Josh Smith. I put all of the code here, but the magic really takes place with the data templates.

Given a few classes to represent the objects we're working with...

using System.Collections.Generic;

namespace WpfTreeViewBinding.Model
{
public class Item
{
public string Name { get; set; }
public string Path { get; set; }
}
}

and...

namespace WpfTreeViewBinding.Model
{
public class FileItem : Item
{

}
}

and...

namespace WpfTreeViewBinding.Model
{
public class DirectoryItem : Item
{
public List<Item> Items { get; set; }

public DirectoryItem()
{
Items = new List<Item>();
}
}
}

I created a recursive method to load up some directories/files...

using System.Collections.Generic;
using System.IO;
using WpfTreeViewBinding.Model;

namespace WpfTreeViewBinding
{
public class ItemProvider
{
public List<Item> GetItems(string path)
{
var items = new List<Item>();

var dirInfo = new DirectoryInfo(path);

foreach(var directory in dirInfo.GetDirectories())
{
var item = new DirectoryItem
{
Name = directory.Name,
Path = directory.FullName,
Items = GetItems(directory.FullName)
};

items.Add(item);
}

foreach(var file in dirInfo.GetFiles())
{
var item = new FileItem
{
Name = file.Name,
Path = file.FullName
};

items.Add(item);
}

return items;
}
}
}

From there it's just a matter of getting the data...

using System.Windows;

namespace WpfTreeViewBinding
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();

var itemProvider = new ItemProvider();

var items = itemProvider.GetItems("C:\\Temp");

DataContext = items;
}
}
}

And displaying it...

<Window x:Class="WpfTreeViewBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Model="clr-namespace:WpfTreeViewBinding.Model"
Title="MainWindow"
Height="350" Width="525">

<Window.Resources>

<HierarchicalDataTemplate DataType="{x:Type Model:DirectoryItem}"
ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Path=Name}" ToolTip="{Binding Path=Path}" />
</HierarchicalDataTemplate>

<DataTemplate DataType="{x:Type Model:FileItem}">
<TextBlock Text="{Binding Path=Name}" ToolTip="{Binding Path=Path}" />
</DataTemplate>

</Window.Resources>

<Grid Margin="8">
<TreeView ItemsSource="{Binding}" />
</Grid>

</Window>

All of the magic really happens with the data templates. I guess the key to the whole thing is using the HierarchicalDataTemplate for any items with hierarchy (i.e. directories).

NOTE 1: I haven't extensively tested this. It hasn't been profiled for performance. I would welcome any feedback though since this is a problem I tried to solve long ago and gave up on. Thanks!

NOTE 2: You'll need to set the hard-coded path to something that makes sense on your system.

Here is a screenshot showing directories and files at different levels...

Sample Image

File System TreeView

If you wanted to stick with the strings something like this would work...

TreeNode root = new TreeNode();
TreeNode node = root;
treeView1.Nodes.Add(root);

foreach (string filePath in myList) // myList is your list of paths
{
node = root;
foreach (string pathBits in filePath.Split('/'))
{
node = AddNode(node, pathBits);
}
}

private TreeNode AddNode(TreeNode node, string key)
{
if (node.Nodes.ContainsKey(key))
{
return node.Nodes[key];
}
else
{
return node.Nodes.Add(key, key);
}
}

WPF Treeview and databinding a Directory Tree

XAML

<TreeView
ItemsSource="{Binding RootDirectoryItems}"
>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:UserDirectory}" ItemsSource="{Binding Items}">
<Label Content="{Binding Name}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:UserFile}">
<Label Content="{Binding Name}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>

RootDirectoryItems is presumed to be a property of the viewmodel, something like this:

public ObservableCollection<Object> RootDirectoryItems { get; } 
= new ObservableCollection<object>();

In the C#, assume the presence of INotifyPropertyChanged boilerplate on all property setters. I added two properties to UserDirectory:

  1. Name, a readonly property which returns just the name segment of DirectoryPath. If DirectoryPath may change at runtime, its setter should call OnPropertyChanged("Name");, so that bindings looking at the Name property will know they need to get the new value. UserFile gained a similar Name property, which comes with the same advice about raising PropertyChanged if that's a possibility.

  2. Items: Again, a readonly property, and you should raise PropertyChanged appropriately if either of the constituent collections changes (handle ICollectionChanged.CollectionChanged, and do likewise in the setters if you have setters). Bindings don't care about the declared type of a property, so it just returns System.Collections.IEnumerable -- it could even return object, and the XAML wouldn't care. But let's be just specific enough, without being so specific as to encourage anybody in C# to try to use the property for anything.

If it were me, I'd almost certainly make UserDirectory and UserFile bare immutable "POCO" classes without INotifyPropertyChanged, and simply repopulate if anything changed on the disk. I might depart from immutability by giving UserDirectory a FileWatcher and having it repopulate itself, if I had some reason to expect directories to change a lot.

So here's the C#:

public class UserDirectory
{
public ObservableCollection<UserFile> Files { get; set; } = new ObservableCollection<UserFile>();
public ObservableCollection<UserDirectory> Subfolders { get; set; } = new ObservableCollection<UserDirectory>();

// Concat demands a non-null argument
public IEnumerable Items { get { return Subfolders?.Cast<Object>().Concat(Files); } }

public String DirectoryPath { get; set; }
public String Name { get { return System.IO.Path.GetFileName(DirectoryPath); } }
}

public class UserFile
{
public String FilePath { get; set; }
public Category Category { get; set; }

public String Name { get { return System.IO.Path.GetFileName(FilePath); } }
}

Your Item class isn't needed, because XAML works by "duck typing".

Here's a simpler variant that also works, because both UserDirectory and UserFile have a Name property, and UserFile's missing Items property is quietly shrugged off.

    <TreeView
ItemsSource="{Binding RootDirectoryItems}"
>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<Label Content="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>

C# How to use treeView to list the directories and subdirectories without showing the root directory?

Try this, it looks real fast here. You can control whether all nodes are expanded or not.. You need to included the LINQ namspace (using System.Linq;)

// somewhere:
string yourRoot = "D:\\";
treeView1.Nodes.AddRange(getFolderNodes(yourRoot, true).ToArray());

private void treeView1_BeforeExpand(object sender, TreeViewCancelEventArgs e)
{
TreeNode tn = e.Node.Nodes[0];
if (tn.Text == "...")
{
e.Node.Nodes.AddRange(getFolderNodes(((DirectoryInfo)e.Node.Tag)
.FullName, true).ToArray());
if (tn.Text == "...") tn.Parent.Nodes.Remove(tn);
}
}

List<TreeNode> getFolderNodes(string dir, bool expanded)
{
var dirs = Directory.GetDirectories(dir).ToArray();
var nodes = new List<TreeNode>();
foreach (string d in dirs)
{
DirectoryInfo di = new DirectoryInfo(d);
TreeNode tn = new TreeNode(di.Name);
tn.Tag = di;
int subCount = 0;
try { subCount = Directory.GetDirectories(d).Count(); }
catch { /* ignore accessdenied */ }
if (subCount > 0) tn.Nodes.Add("...");
if (expanded) tn.Expand(); // **
nodes.Add(tn);
}
return nodes;
}

If you are sure you always want to see all levels expaded from the start you can use this function and delete the BeforeExpand code:

List<TreeNode> getAllFolderNodes(string dir)
{
var dirs = Directory.GetDirectories(dir).ToArray();
var nodes = new List<TreeNode>();
foreach (string d in dirs)
{
DirectoryInfo di = new DirectoryInfo(d);
TreeNode tn = new TreeNode(di.Name);
tn.Tag = di;
int subCount = 0;
try { subCount = Directory.GetDirectories(d).Count(); }
catch { /* ignore accessdenied */ }
if (subCount > 0)
{
var subNodes = getAllFolderNodes(di.FullName);
tn.Nodes.AddRange(subNodes.ToArray());
}
nodes.Add(tn);
}
return nodes;
}

You call it as before:

string yourRoot = "D:\\";
Cursor.Current = Cursors.WaitCursor;
treeView1.Nodes.AddRange(getAllFolderNodes(yourRoot).ToArray());
Cursor.Current = Cursors.Default;


Related Topics



Leave a reply



Submit