Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / WPF
Article

A Simple WPF Explorer Tree

Rate me:
Please Sign up or sign in to vote.
4.70/5 (105 votes)
8 Nov 20076 min read 470.8K   17.7K   163   89
A Simple WPF Explorer Tree

Contents

Introduction

I am still getting to grips with WPF, and last night, as part of a larger article that I am still working on, I wanted to create a simple (basic version) of an explorer tree, which shows drives and folders. I wanted to display a drive image if the TreeViewItem is a drive, and a folder image otherwise. Sounds easy right. Wrong, it turned out to be quite tricky, well at least it was for me. So I thought that as the big article where this technique is used is still being written, I would break out the tree view implementation into a smaller article (this one). I think it's probably going to be a fairly common requirement to display different images for the current TreeViewItem based on some condition. So that's what this article is all about.

Solving the Problem

The finished product looks like this:

Image 1

Really simple, isn't it.

So How did I Get the WPF TreeView to do that

The first step is to get it to display the correct tree, which is really down to the following two methods.

C#
private void Window_Loaded(object sender, RoutedEventArgs e)
{
    foreach (string s in Directory.GetLogicalDrives())
    {
        TreeViewItem item = new TreeViewItem();
        item.Header = s;
        item.Tag = s;
        item.FontWeight = FontWeights.Normal;
        item.Items.Add(dummyNode);
        item.Expanded += new RoutedEventHandler(folder_Expanded);
        foldersItem.Items.Add(item);
    }
}

void folder_Expanded(object sender, RoutedEventArgs e)
{
    TreeViewItem item = (TreeViewItem)sender;
    if (item.Items.Count == 1 && item.Items[0] == dummyNode)
    {
        item.Items.Clear();
        try
        {
            foreach (string s in Directory.GetDirectories(item.Tag.ToString()))
            {
                TreeViewItem subitem = new TreeViewItem();
                subitem.Header = s.Substring(s.LastIndexOf("\\") + 1);
                subitem.Tag = s;
                subitem.FontWeight = FontWeights.Normal;
                subitem.Items.Add(dummyNode);
                subitem.Expanded += new RoutedEventHandler(folder_Expanded);
                item.Items.Add(subitem);
            }
        }
    catch (Exception) { }
    }
}

That's enough to get us the drive/folder hierarchy for the TreeView. Next step, I wanted images for the individual TreeViewItems.

By default, the WPF TreeView control does NOT display images, for example the image below shows what the WPF control looks like out of the box (Note I am using Vista, so it may look slightly different on XP)

Image 2

This isn't what I wanted. So I started to look around to see if there was an Image property or something like that on the TreeViewItem, and guess what, there isn't. But of course WPF lets us change the look and feel of controls using Styles/Templates. So that's a good place to start, to maybe develop a Style/Template. Note: I would recommend not using Expression Blend for this task, as it creates about 200 lines of XAML the minute you decide to start editing the WPF TreeView control using Expression Blend, and that's before you've even changed it. So it will likely be more. Don't get me wrong. Expression Blend is handy but for some things like Style/Template editing VS2005/VS2008 and hard crafted code are the way to go, you get much less code to do the job.

Ok rant over, so we need to create some sort of Style for the WPF TreeView control, so I started going down that road and ended up with the following:

XML
<TreeView x:Name="foldersItem"
          SelectedItemChanged="foldersItem_SelectedItemChanged"
          Width="Auto" Background="#FFFFFFFF"
          BorderBrush="#FFFFFFFF"
          Foreground="#FFFFFFFF">
    <TreeView.Resources>
        <Style TargetType="{x:Type TreeViewItem}">
            <Setter Property="HeaderTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <Image Name="img"
                                   Width="20"
                                   Height="20"
                                   Stretch="Fill"
                                   Source="Images/diskdrive.png"/>
                            <TextBlock Text="{Binding}" Margin="5,0" />
                        </StackPanel>
                    </DataTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </TreeView.Resources>
</TreeView>

This style ends up with the following, where we now have some images against our TreeViewItem, which is cool. We're getting there. But all the images are the same. But that's because this style is using a fixed path for all the Image Source properties. So it's bound not to work. Grrr. Maybe there's something more that can be done in the style. As it happens that's exactly what is done. Let's see.

Image 3

I'll just include the part of the Style that is different from that shown above.

C#
<Image Name="img"  Width="20" Height="20" Stretch="Fill"
    Source="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
    AncestorType={x:Type TreeViewItem}},
    Path=Header,
    Converter={x:Static local:HeaderToImageConverter.Instance}}"
/>

Now I have to say this is probably the most complicated bit of binding code that I've ever written in XAML. So what it does then, eh?

Well basically it sets the Image Source property to be bound to the TreeViewItems Header property (The Header property, is the one that holds the text shown on the rendered TreeView control, so it would hold strings like c:\\, Program Files, Windows etc. etc.

But what use is that, these c:\\, Program Files, Windows string values aren't Image Source Uri's, are they. They aren't even close, an Image Source Uri, should be something like C:\Windows\Web\Azul.jpg or something shouldn't it?

Well yeah they should actually. But WPF Databinding has one last trick up its very long and vacuumous (is that a word, it should be, I reckon) sleeve, Value Converters. Value Converters allow us to create a class that will use the original DataBound value and return a different object that will be used as the final binding value.

This is the trick that I use to get the image source to point to the correct location. Basically in the Image Source binding shown above, I also specify a converter called HeaderToImageConverter which I use to check whether the actual TreeViewItems Header property contains a \ character. And if it does, I consider that TreeViewItem to be a diskdrive, so I return a diskdrive Image Source Uri, otherwise I return a folder Image Source Uri. This may become clearer once you see the actual converter.

C#
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media.Imaging;

namespace WPF_Explorer_Tree
{
    #region HeaderToImageConverter

    [ValueConversion(typeof(string), typeof(bool))]
    public class HeaderToImageConverter : IValueConverter
    {
        public static HeaderToImageConverter Instance =
            new HeaderToImageConverter();

        public object Convert(object value, Type targetType,
            object parameter, CultureInfo culture)
        {
            if ((value as string).Contains(@"\"))
            {
                Uri uri = new Uri
                ("pack://application:,,,/Images/diskdrive.png");
                BitmapImage source = new BitmapImage(uri);
                return source;
            }
            else
            {
                Uri uri = new Uri("pack://application:,,,/Images/folder.png");
                BitmapImage source = new BitmapImage(uri);
                return source;
            }
        }

        public object ConvertBack(object value, Type targetType,
            object parameter, CultureInfo culture)
        {
            throw new NotSupportedException("Cannot convert back");
        }
    }

    #endregion // HeaderToImageConverter
}

So it can be seen that the HeaderToImageConverter accepts a string and returns an object. Basically the value parameter coming in is the TreeViewItems Header property, which we specified in the original binding. We don't care about the other parameters, but the Convert and ConvertBack method signature are dictated by the IValueConverter interface, so we must have the correct method signature, despite which parameters we actually end up using.

Anyway so the value parameter = the TreeViewItems Header property, that's all we care about right now. The next step was to see if this value (the TreeViewItems Header) contains a \ character, and if it does return the diskdrive Image Source Uri, otherwise I return a folder Image Source Uri. So that's pretty easy now, we just do a little string test, and then create the appropriate Uri. The only tricky bit here is that because the images are actually set to have a build action in Visual Studio of "Resource", we need to get the image path out of the application.

"Where does that awful tripple comma syntax come from?

"pack://application:,,,/Images/diskdrive.png"

What the heck does that mean.

The pack URI format is part of the XML Paper Specification (XPS), which can be found at http://www.microsoft.com/whdc/xps/default.mspx

The specified format is pack://packageURI/partPath

The packageURI is actually a URI within a URI, so its encoded by converting its forward slashes into commas. This packageURI could point to an XPS document, such as file:///C:/Document.xps encoded as file:,,,c:,Documenr.xps, Or, in WPF programs it can be one of two URIs treated specially by the platform

  • siteOfOrigin:/// (encoded as siteOfOrigin:,,,)
  • application:/// (encoded as application:,,,)

Therefore, the tripple commas are actually encoded forward slashes bit place holders for original parameters. (Note that these can also be specified with two slashes/commas rather than three).

The application:/// package is implicitly used by all the resource references that don't use siteOfOrigin. In other words, the following URI specified in XAML:

logo.jpg

is really just shorthand notation for

pack://application:,,,/logo.jpg

and this URI

MyDll;Component/logo.jpg

is shorthand notation for:

pack://application:,,,/MyDll;Component/logo.png

You could use these longer and more explicit URIs in XAML, but there's no good reason to."

Windows Presentation Foundation Unleashed. Adam Nathan, Sams. 2007

That's It

I hope this helps anyone that wants to create a better fully functional, explorer tree in WPF. This one satisfied my requirements.

What do you Think ?

I would just like to ask, if you liked the article please vote for it, and leave some comments, as it lets me know if the article was at the right level or not, and whether it contained what people need to know.

History

  • v1.0 09/11/07: Initial issue

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
QuestionAwesome Job! Pin
mkeysaer29-Apr-22 5:45
mkeysaer29-Apr-22 5:45 
QuestionDistribution Pin
_GeMine_12-Mar-18 7:41
_GeMine_12-Mar-18 7:41 
QuestionExpand tree programmatically Pin
andres87_324-Apr-17 7:58
andres87_324-Apr-17 7:58 
PraiseExactly what I was looking for, Enjoy your 5 stars Pin
Mr Guy1243-Nov-16 7:52
Mr Guy1243-Nov-16 7:52 
QuestionWant to populate selected directory (from tree view) contents into listview Pin
Member 1205103319-Jan-16 7:58
Member 1205103319-Jan-16 7:58 
QuestionNeed your help Pin
Member 1166425920-Dec-15 20:27
Member 1166425920-Dec-15 20:27 
AnswerRe: Need your help Pin
Member 1234741615-Mar-16 9:59
Member 1234741615-Mar-16 9:59 
GeneralRe: Need your help Pin
Sacha Barber15-Mar-16 10:05
Sacha Barber15-Mar-16 10:05 
QuestionI am getting an error when I try to implement this in my app Pin
Ibizanhound2-Aug-15 5:43
Ibizanhound2-Aug-15 5:43 
AnswerRe: I am getting an error when I try to implement this in my app Pin
Sacha Barber2-Aug-15 21:15
Sacha Barber2-Aug-15 21:15 
QuestionRe: I am getting an error when I try to implement this in my app Pin
Ibizanhound3-Aug-15 2:11
Ibizanhound3-Aug-15 2:11 
AnswerRe: I am getting an error when I try to implement this in my app Pin
Sacha Barber3-Aug-15 3:17
Sacha Barber3-Aug-15 3:17 
GeneralRe: I am getting an error when I try to implement this in my app Pin
Ibizanhound3-Aug-15 3:25
Ibizanhound3-Aug-15 3:25 
GeneralRe: I am getting an error when I try to implement this in my app Pin
Ibizanhound3-Aug-15 10:06
Ibizanhound3-Aug-15 10:06 
GeneralRe: I am getting an error when I try to implement this in my app Pin
Ibizanhound3-Aug-15 10:56
Ibizanhound3-Aug-15 10:56 
GeneralRe: I am getting an error when I try to implement this in my app Pin
ronen4427-Feb-19 5:03
ronen4427-Feb-19 5:03 
QuestionNice thanks Pin
Member 1033989123-Sep-14 21:42
Member 1033989123-Sep-14 21:42 
QuestionDummy node Pin
Clski23-Jan-14 23:50
Clski23-Jan-14 23:50 
AnswerRe: Dummy node Pin
marcelodiniz11-Feb-14 8:07
marcelodiniz11-Feb-14 8:07 
QuestionGood but could be better Pin
leiyangge10-Nov-13 16:52
leiyangge10-Nov-13 16:52 
QuestionFantastic Pin
Michael J Hill - OH9-Jul-13 12:38
Michael J Hill - OH9-Jul-13 12:38 
GeneralOlder article but still relevant, thanks. Pin
Jab19576-Jul-13 19:16
Jab19576-Jul-13 19:16 
QuestionFile Explorer tree view with checkbox for select multiple path in wpf Pin
sumitk.cadc4-Jun-13 20:20
sumitk.cadc4-Jun-13 20:20 
GeneralVery Clear. Pin
90liuliuqiu16-Jan-13 19:59
90liuliuqiu16-Jan-13 19:59 
Thanks for the article.
QuestionWere you able to implement Incremental Search? Pin
MarkLoboo14-Jul-12 7:07
MarkLoboo14-Jul-12 7:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.