Click here to Skip to main content
15,868,016 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.9K   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

 
QuestionWhat do you have to do if you only want to have the content of one directory map? Pin
the original stinger27-Nov-11 23:02
the original stinger27-Nov-11 23:02 
AnswerRe: What do you have to do if you only want to have the content of one directory map? Pin
Sacha Barber28-Nov-11 0:11
Sacha Barber28-Nov-11 0:11 
GeneralRe: What do you have to do if you only want to have the content of one directory map? Pin
Member 842698629-Mar-12 1:46
Member 842698629-Mar-12 1:46 
GeneralMy vote of 5 Pin
Deep Ash27-Dec-10 21:29
Deep Ash27-Dec-10 21:29 
GeneralVery good [modified] Pin
jian.zeng6-Dec-10 22:46
jian.zeng6-Dec-10 22:46 
GeneralNice one Pin
Prafulla Hunde17-Nov-10 18:26
Prafulla Hunde17-Nov-10 18:26 
GeneralVerry very helpful Pin
Member 220236126-Oct-10 6:21
Member 220236126-Oct-10 6:21 
GeneralGreat ! Pin
Aybe21-Sep-09 4:27
Aybe21-Sep-09 4:27 
Thanks !
QuestionHow to display treeview from database Pin
Jasmine Pomelo13-May-09 18:50
Jasmine Pomelo13-May-09 18:50 
GeneralMy vote of 1 Pin
winSharp9318-Feb-09 4:38
winSharp9318-Feb-09 4:38 
GeneralNice job Pin
Rami Shareef4-Feb-09 7:34
Rami Shareef4-Feb-09 7:34 
GeneralRe: Nice job Pin
Sacha Barber4-Feb-09 9:23
Sacha Barber4-Feb-09 9:23 
GeneralRe: Nice job Pin
Rami Shareef4-Feb-09 21:58
Rami Shareef4-Feb-09 21:58 
GeneralRe: Nice job Pin
Sacha Barber5-Feb-09 0:13
Sacha Barber5-Feb-09 0:13 
GeneralDodge the horrible ,,, syntax Pin
captainplanet01239-Nov-08 23:50
captainplanet01239-Nov-08 23:50 
GeneralRe: Dodge the horrible ,,, syntax Pin
Sacha Barber10-Nov-08 2:15
Sacha Barber10-Nov-08 2:15 
GeneralExcellent work, thank you. Pin
Nji, Klaus30-Oct-08 5:22
Nji, Klaus30-Oct-08 5:22 
GeneralRe: Excellent work, thank you. Pin
Sacha Barber30-Oct-08 7:17
Sacha Barber30-Oct-08 7:17 
GeneralJust a proposal.. Pin
ezazazel1-Oct-08 11:53
ezazazel1-Oct-08 11:53 
GeneralRe: Just a proposal.. Pin
Sacha Barber1-Oct-08 21:48
Sacha Barber1-Oct-08 21:48 
GeneralThreaded WPF Explorer Pin
Kavan Shaban9-Mar-08 15:26
Kavan Shaban9-Mar-08 15:26 
GeneralRe: Threaded WPF Explorer Pin
captainplanet01239-Nov-08 23:52
captainplanet01239-Nov-08 23:52 
GeneralRe: Threaded WPF Explorer Pin
Kavan Shaban10-Nov-08 4:25
Kavan Shaban10-Nov-08 4:25 
Generalfolder_Expanded Pin
robmar26-Feb-08 2:28
robmar26-Feb-08 2:28 
GeneralRe: folder_Expanded Pin
Sacha Barber26-Feb-08 2:58
Sacha Barber26-Feb-08 2:58 

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.