Populate Treeview from List of File Paths in Wpf

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

Populate TreeView from list of paths with values

Here's one approach to your question. Comments are in code.
Works with your sample data but I cannot guarantee it will work in some other cases :)

Main method is PopulateTreeView() so call it from, in example, form's Load event. Also, there is helper method FindNode which is used to search through first level of nodes to find if node with supplied text exists.

If you have additional questions, feel free to ask.

private void PopulateTreeView()
{
//read from file
var lines = File.ReadAllLines(@"c:\temp\tree.txt");
//go through all the lines
foreach (string line in lines)
{
//split by dot to get nodes names
var nodeNames = line.Split('.');
//TreeNode to remember node level
TreeNode lastNode = null;

//iterate through all node names
foreach (string nodeName in nodeNames)
{
//values for name and tag (tag is empty string by default)
string name = nodeName;
string tagValue = string.Empty;
//if node is in format "name=value", change default values of name and tag value. If not,
if (nodeName.Contains("="))
{
name = nodeName.Split('=')[0];
tagValue = nodeName.Split('=')[1];
}

//var used for finding existing node
TreeNode existingNode = null;
//new node to add to tree
TreeNode newNode = new TreeNode(name);
newNode.Tag = tagValue;
//collection of subnodes to search for node name (to check if node exists)
//in first pass, that collection is collection of treeView's nodes (first level)
TreeNodeCollection nodesCollection = treeView1.Nodes;

//with first pass, this will be null, but in every other, this will hold last added node.
if (lastNode != null)
{
nodesCollection = lastNode.Nodes;
}

//look into collection if node is already there (method checks only first level of node collection)
existingNode = FindNode(nodesCollection, name);
//node is found? In that case, skip it but mark it as last "added"
if (existingNode != null)
{
lastNode = existingNode;
continue;
}
else //not found so add it to collection and mark node as last added.
{
nodesCollection.Add(newNode);
lastNode = newNode;
}
}
}

treeView1.ExpandAll();
}

private TreeNode FindNode(TreeNodeCollection nodeCollectionToSearch, string nodeText)
{
var nodesToSearch = nodeCollectionToSearch.Cast<TreeNode>();
var foundNode = nodesToSearch.FirstOrDefault(n => n.Text == nodeText);
return foundNode;
}

How to create a TreeView from a list of objects in WPF

You need to use properties instead of variables in the WorkFlowScriptIDDataStore class.Try the below sample.

 <TreeView x:Name="treeView">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:WorkFlowScriptIDDataStore}" ItemsSource="{Binding Subcategories}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>

public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
WorkFlowScriptIDDataStore workFlowScriptIdDataStore = new WorkFlowScriptIDDataStore()
{
Id="1",
Name = "Name",
ParentId = "-1",
Subcategories = new List<WorkFlowScriptIDDataStore>()
{
new WorkFlowScriptIDDataStore()
{
Id="1",
Name = "Name23",
ParentId = "-1",
},
new WorkFlowScriptIDDataStore()
{
Id="2",
Name = "Name1",
ParentId = "-1",
},
}
};

List<WorkFlowScriptIDDataStore> lst = new List<WorkFlowScriptIDDataStore>() { workFlowScriptIdDataStore };

treeView.ItemsSource = lst;
}
}

public class WorkFlowScriptIDDataStore
{
public string Id { get; set; }
public string Name { get; set; }
public string ParentId { get; set; }

public List<WorkFlowScriptIDDataStore> Subcategories { get; set; }
public void Clear()
{
Id = "";
Name = "";
ParentId = "";
}
}

How to populate treeview with file path which is saved in database

Depending on your framework version, maybe you can try something like this :

public void ProcessPath(IEnumerable<String> path,  TreeNodeCollection nodes)
{
if (!path.Any())
return;
var node = nodes.Cast<TreeNode>().FirstOrDefault(n => n.Text == path.First());
if (node == null)
{
node = new TreeNode(text: path.First());
nodes.Add(node);
}
ProcessPath(path.Skip(1),node.ChildNodes);
}

public void CreateTreeView()
{
foreach (string field in MyDataBase.FieldsInMyColumn())
ProcessPath(field.Split('\\'),myTreeView.Nodes);
}

If you really have a huge amount of rows, you should probably look for a solution where you only load the child nodes upon click on a node

Populate ListBox on Selecting TreeView node in WPF using MVVM

First add a Files collection class to your folder class (used in the tree view)

public class FolderItem
{
// other class code

private ObservableCollection<File> _Files = null;

public ObservableCollection<File> Files
{
get
{
if (_Files == null) _Files = GetFiles();
return _Files;
}
set
{
_Files = value;
}
}

}

Then bind the listbox to the selected treeview item.

<ListBox ItemsSource="{Binding ElementName=myTreeView, Path=SelectedItem.Files}"/>

Avoiding root Node in Populating TreeView with a List of File Paths

I assume that you are putting that TreeNode into a TreeView at some point, like this:

treeView.Nodes.Add(MakeTreeFromPaths(pathList));

Instead of adding that root node, you could add all its children, like this:

foreach(var node in MakeTreeFromPaths(pathList).Nodes)
{
treeView.Nodes.Add(node);
}

This is not very beautiful though. It would be even nicer if you would take the TreeView as parameter and populate it directly or if you would return a list of nodes instead.



Related Topics



Leave a reply



Submit