populate treeview from list of file paths in wpf
Asked Answered
D

2

30

There are several examples of how to populate a tree view from a collection of file paths such as this or this other example. I cannot seem to find such example for WPF. I know I can integrate windows forms and use a different control in order to make it work but it will be nice if I could do the same thing with a wpf treeview control. The tree view that I want to construct consists of about 50,000 files therefore I think it will be better if it is bind it to something. But first before binding it, I think it will be helpful to construct one based on a List of strings (strings contains the paths of files).

Docilla answered 20/6, 2011 at 17:24 Comment(1)
You wouldn't have to populate the entire collection first. You could have the tree control fetch on demand. I've used teleriks tree control for this.Wrecker
H
69

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...

enter image description here

Heymann answered 3/7, 2011 at 3:14 Comment(8)
Jhon thanks a lot! your answer looks great. I am looking forward to test your solution when I come back tomorrow morning. Don't work in any more edits... Let me test it first.Docilla
This is a good answer. However, no need to create an Item and FileItem class. You can create a local DataTemplate for both the System.IO DirectoryInfo and FileInfo classes.West
Now enhance it to use lazy loading :]Nth
How does the ItemSource for the TreeView know its Binding?Zoara
@Zoara the DataContext = items assignment in the window's constructor sets the data context - {Binding} without a path is the DataContext, i.e. the items.Milly
Works great! I would make asynchronous because this code incudes I/O access.Ileneileo
If you use this as Readonly you can use DirInfo and FileInfo instead providing just name and path. FileInfo has properties to extract Name and Path, but these are readonly. I also suggest to use ObservableCollection instead of List.Lanyard
How can I do in the case of wanting to filter the files?Tetrad
I
5

Small extension for previous solution: I added xaml code to support the icons, and support for switching between icons for opened and closed folder:

 <HierarchicalDataTemplate DataType="{x:Type viewModels:SourceControlDirecoryViewModel}"
                                  ItemsSource="{Binding Items}">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="5" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <Image Width="16"
                       Height="16"
                       Source="{StaticResource ImageSourceFolderClosed16x16}"
                       x:Name="img" />
                <TextBlock Text="{Binding Path=Name}"
                           ToolTip="{Binding Path=Path}"
                           Grid.Column="2" />
            </Grid>
            <DataTemplate.Triggers>
                <DataTrigger Binding="{Binding IsExpanded, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type TreeViewItem}}}"
                             Value="True">
                    <Setter Property="Source"
                            TargetName="img"
                            Value="{StaticResource ImageSourceFolderOpened16x16}" />
                </DataTrigger>
            </DataTemplate.Triggers>
        </HierarchicalDataTemplate>
Ileneileo answered 27/6, 2016 at 13:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.