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...
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
:
Name
, a readonly property which returns just the name segment ofDirectoryPath
. IfDirectoryPath
may change at runtime, its setter should callOnPropertyChanged("Name");
, so that bindings looking at theName
property will know they need to get the new value.UserFile
gained a similarName
property, which comes with the same advice about raisingPropertyChanged
if that's a possibility.Items
: Again, a readonly property, and you should raisePropertyChanged
appropriately if either of the constituent collections changes (handleICollectionChanged.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 returnsSystem.Collections.IEnumerable
-- it could even returnobject
, 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
Reading Excel Files as a Server Process
Accessing Properties Through Generic Type Parameter
How to Change Time in Datetime
How to Tell When Httpclient Has Timed Out
Why Does Path.Combine Not Properly Concatenate Filenames That Start with Path.Directoryseparatorchar
Checking If a String Array Contains a Value, and If So, Getting Its Position
Use of SQLparameter in SQL Like Clause Not Working
Watermark in System.Windows.Forms.Textbox
System.Data.Sqlclient.Sqlexception: Login Failed for User
Shortcut for "Null If Object Is Null, or Object.Member If Object Is Not Null"
Lambda Variable Capture in Loop - What Happens Here
How to Urlencode Without Using System.Web
How to Check If Object Already Exists in a List