Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Simple WPF Explorer Tree

0.00/5 (No votes)
8 Nov 2007 3  
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:

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.

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)

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:

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

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

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

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