Friday, January 25, 2013

PART 1: C# WPF TreeView File Explorer with System Icons

How to make a file system explorer with system icons using WPF TreeView control?

Actually, this blog is one of the most exciting topic we have as of now. The purpose of this blog is to guide you how to implement a file system explorer control using C# WPF TreeView control that displays the file system icons and its other file system information. See the image below the target output of this blog.


In order for you to understand further how did we address the implementation, we are requiring you to visit the list of blogs below. They are the pre-requisite blogs and will guide you how did we re-use the existing implementation. We are looking forward that you will not neglect them as it will guide you to fully understand how the process and flow of the source code is happening.

Below are the list pre-requisite blogs you need to read.

Now that you're finish with the list of topics above, then you're ready to go.

Creating a Wrapper Class

First of all, in C# file system information, the class FileSystemInfo is the base class of the DirectoryInfo and FileInfo class. For us to create a dynamic wrapper of both FileInfo and DirectoryInfo, we should somehow polymorph the implementation by creating a wrapper class for the FileSystemInfo object. Let us call this class FileSystemObjectInfo. See below the wrapper class for FileSystemInfo.

public class FileSystemObjectInfo : BaseObject
{
    public FileSystemObjectInfo(FileSystemInfo info)
    {  
    }

    public FileSystemObjectInfo(DriveInfo drive)
        : this(drive.RootDirectory)
    {
    }
}

Please note that we have inherited the functionality of the BaseObject class we have discussed from the Implementing a Flexible Base Object.

Now that we have this wrapper class, we also need to create an additional dummy class for this object. Let us call this dummy class DummyFileSystemObjectInfo.

private class DummyFileSystemObjectInfo : FileSystemObjectInfo
{
    public DummyFileSystemObjectInfo()
        : base(new DirectoryInfo("DummyFileSystemObjectInfo"))
    {
    }
}

The purpose of this dummy is just a dummy item for the Children property of our wrapper class. This dummy item will be removed once the user interacts on the TreeViewItem node Expanded item.

Now we have to create the important properties that we will be using in this class. See below each property needed.

public ObservableCollection<FileSystemObjectInfo> Children
{
    get { return base.GetValue<ObservableCollection<FileSystemObjectInfo>>("Children"); }
    private set { base.SetValue("Children", value); }
}

public ImageSource ImageSource
{
    get { return base.GetValue<ImageSource>("ImageSource"); }
    private set { base.SetValue("ImageSource", value); }
}

public bool IsExpanded
{
    get { return base.GetValue<bool>("IsExpanded"); }
    set { base.SetValue("IsExpanded", value); }
}

public FileSystemInfo FileSystemInfo
{
    get { return base.GetValue<FileSystemInfo>("FileSystemInfo"); }
    private set { base.SetValue("FileSystemInfo", value); }
}

private DriveInfo Drive
{
    get { return base.GetValue<DriveInfo>("Drive"); }
    set { base.SetValue("Drive", value); }
}

All properties above is very useful on our implementation. See below their used.

Children: a collection of FileSystemObjectInfo in which it will handle all the actual children of the actual directory of the file system.

ImageSource: an image property corresponds to the current folder or file system icon.

IsExpanded: this property participates in WPF binding. This is used to determine the current TreeView node state whether it is expanded or collapsed.

FileSystemInfo: this property will handle the current bound object such as DirectoryInfo or FileInfo. In this property we have polymorph(ed) both System.IO classes.

Drive: this property will handle the current drive info if we are exploring the system drives.

Furthermore, we have to create the important methods to do the actual file system exploration. Note that this wrapper class will be used by our binding in WPF TreeView control to display the content inside it.

private void AddDummy()
{
    this.Children.Add(new DummyFileSystemObjectInfo());
}

private bool HasDummy()
{
    return !object.ReferenceEquals(this.GetDummy(), null);
}

private DummyFileSystemObjectInfo GetDummy()
{
    var list = this.Children.OfType<DummyFileSystemObjectInfo>().ToList();
    if (list.Count > 0) return list.First();
    return null;
}

private void RemoveDummy()
{
    this.Children.Remove(this.GetDummy());
}

You will see four methods above that is related to a dummy object manipulation. See below our used on each method.

AddDummy: adds a dummy object in the Children property of the FileSystemObjectInfo.

HasDummy: checks whether there is dummy object in the Children property of the FileSystemObjectInfo.

GetDummy: returns the current added DummyFileSystemObjectInfo object.

RemoveDummy: removes the DummyFileSystemObjectInfo object that was already added in the Children property.

For your information, our class FileSystemObjectInfo is participating the event notification by inheriting in the BaseObject class from our previous blog. With this, we have to listen in the PropertyChanged event of the class so we know in what property will we load the child folders and files of the current file system object. In the constructor, we need to add the listener and implement the event handler method. See below our codes.

In the constructor.

public FileSystemObjectInfo(FileSystemInfo info)
{
    this.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(FileSystemObjectInfo_PropertyChanged);
}

The event handler of PropertyChanged event.

void FileSystemObjectInfo_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
    if (this.FileSystemInfo is DirectoryInfo)
    {
        if (string.Equals(e.PropertyName, "IsExpanded", StringComparison.CurrentCultureIgnoreCase))
        {
            if (this.IsExpanded)
            {
                this.ImageSource = FolderManager.GetImageSource(this.FileSystemInfo.FullName, ItemState.Open);
                if (this.HasDummy())
                {
                    this.RemoveDummy();
                    this.ExploreDirectories();
                    this.ExploreFiles();
                }
            }
            else
            {
                this.ImageSource = FolderManager.GetImageSource(this.FileSystemInfo.FullName, ItemState.Close);
            }
        }
    }
}

You will notice two new methods named ExploreDirectories and ExploreFiles. This two methods is very important when exploring the child folders and files of the file system. See below our implementation for both methods.

private void ExploreDirectories()
{
    if (!object.ReferenceEquals(this.Drive, null))
    {
        if (!this.Drive.IsReady) return;
    }
    try
    {
        if (this.FileSystemInfo is DirectoryInfo)
        {
            var directories = ((DirectoryInfo)this.FileSystemInfo).GetDirectories();
            foreach (var directory in directories.OrderBy(d => d.Name))
            {
                if (!object.Equals((directory.Attributes & FileAttributes.System), FileAttributes.System) &&
                    !object.Equals((directory.Attributes & FileAttributes.Hidden), FileAttributes.Hidden))
                {
                    this.Children.Add(new FileSystemObjectInfo(directory));
                }
            }
        }
    }
    catch
    {
        /*throw;*/
    }
}

private void ExploreFiles()
{
    if (!object.ReferenceEquals(this.Drive, null))
    {
        if (!this.Drive.IsReady) return;
    }
    try
    {
        if (this.FileSystemInfo is DirectoryInfo)
        {
            var files = ((DirectoryInfo)this.FileSystemInfo).GetFiles();
            foreach (var file in files.OrderBy(d => d.Name))
            {
                if (!object.Equals((file.Attributes & FileAttributes.System), FileAttributes.System) &&
                    !object.Equals((file.Attributes & FileAttributes.Hidden), FileAttributes.Hidden))
                {
                    this.Children.Add(new FileSystemObjectInfo(file));
                }
            }
        }
    }
    catch
    {
        /*throw;*/
    }
}

Now, we have nothing left to explain on the content of this class. All we have to do is to loading the actual image and some initialization in the constructor. See below our new constructor implementation.

public FileSystemObjectInfo(FileSystemInfo info)
{
    if (this is DummyFileSystemObjectInfo) return;
    this.Children = new ObservableCollection<FileSystemObjectInfo>();
    this.FileSystemInfo = info;
    if (info is DirectoryInfo)
    {
        this.ImageSource = FolderManager.GetImageSource(info.FullName, ItemState.Close);
        this.AddDummy();
    }
    else if (info is FileInfo)
    {
        this.ImageSource = FileManager.GetImageSource(info.FullName);
    }
    this.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(FileSystemObjectInfo_PropertyChanged);
}

So what the code inside the constructor is doing? First, we initialized the Children property and then set the handled FileSystemInfo property into the argument of our constructor. After that, we checked whether the FileSystemInfo is a DirectoryInfo or FileInfo. If it is a DriveInfo object we have to load the actual folder image by using the FolderManager discussed in the Displaying System Icon in C#, WPF. However, if it is a FileInfo object, then we will use the FileManager to load the actual file image.

And now we are complete with the wrapper implementation.

Please visit the PART 2: C# WPF TreeView File Explorer with System Icons for the continuation.

5 comments:

  1. can I download the source code?

    ReplyDelete
  2. Great post! Thanks!

    ReplyDelete
  3. Can you please share the source for this one? It is really advanced and I still can not fit all pieces.

    ReplyDelete

Place your comments and ideas