Me Myself & C#

Manoj Garg’s Tech Bytes – What I learned Today

WPF 101 : A Simple Windows Explorer like Directory Browser

Posted by Manoj Garg on March 27, 2009

I just started learning about WPF. It is always good to learn something by developing some sample application which has something related to what you are learning.

One of my colleague suggested me to develop a Windows Explorer kind of file system explorer using WPF. something like as show in figure below

task

 

Requirements:

  1. It should have a left tree showing the directory tree rooted at “My Computer
  2. It should have a right pane showing all the children of the current node selected in the left side tree.
  3. Directories in the right side node should be navigable i.e. use can use the mouse double click or Enter key to see its contents. If user clicks on a directory then s/he should see the children of this directory and the left side tree should be in sync accordingly. Or, if user clicks on a file then that file should open in corresponding application.
  4. Only mode of handling the user event allowed is “DataBinding

My Approach:
First I will be using Model-View-ViewModel pattern for WPF
I will use WPF Treeview for left side directory Tree and a listbox for the right side pane for showing the children of the current selected node.

Model-View-ViewModel
This pattern is the most widely used and accepted approach for using in WPF application. This allows a developer to separate his logic with the presentation.
I will not go into the details of this design pattern. A detail description of it is available here by Josh Smith.
Following section discusses how I divided this sample app into these 3 logical parts.

Model:
We need a data source which provides us the information about current file system file like Logical Drives, Child Directories of a Directory or Files under a directory. I will be creating a class named FileSystemExplorerService which will serve as my interaction point for any information related to current file system. Below is the class diagram for this class

Model_ClassDiagram

View:
we will be need following views:

  1. View for Left Tree : File “View\FileSystemTree.xaml” contains this view.
  2. View for Right Pane : File “View\DirectoryViewer.xaml” contains this view.
  3. View for outer window: This view is for containing the above two views to act as an main window for the application. File “View\ExplorerWindow.xaml” contains this view.

ViewModel:
We will have separate viewmodel for each View in the application. Since all viewmodel need a notification mechanism, I will also be creating a base class from which all the view models will drive. Following are the classes in view model namespace.

  1. ViewModelBase class: This base class is responsible for handling the notification mechanism. It implements INotifyPropertyChanged interface. This is an abstract class.
  2. FileExplorerViewModel Class: this class is the Viewmodel for the left side tree.
  3. DirectoryViewerViewModel Class: This class is the viewmodel for the right side pane.
  4. ExplorerWindowViewModel Class: This class is the View model for the overall outer window.
  5. DirInfo Class: This class is the entity class which has information about a file/Directory like its name, path, children etc. we will be binding the Tree and the list box to a collection of DirInfo class.

Below is the class diagram for all the Viewmodel classes.

ViewModel_ClassDiagram

Implementation

Model Implementation

Following image shows the directory structure for the file explorer.

directorystructure

Lets start with the code for Model. Our model has just one class named “FileSystemExplorerService” which has three methods

  1. GetRootDirectories : This method returns the logical drives in the local file system.
  2. GetChildDirectories : This method returns the sub directories of the directory path passed.
  3. GetChildFiles : This method returns the files under the directory path passed.

Following is the code snipped for this class

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5: using System.IO;
   6: using System.Diagnostics;
   7:
   8: namespace FileExplorer.Model
   9: {
  10:     /// <summary>
  11:     /// Class to get file system information
  12:     /// </summary>
  13:     public class FileSystemExplorerService
  14:     {
  15:         /// <summary>
  16:         /// Gets the list of files in the directory Name passed
  17:         /// </summary>
  18:         /// <param name="directory">The Directory to get the files from</param>
  19:         /// <returns>Returns the List of File info for this directory.
  20:         /// Return null if an exception is raised</returns>
  21:         public static IList<FileInfo> GetChildFiles(string directory)
  22:         {
  23:             try
  24:             {
  25:                 return (from x in Directory.GetFiles(directory)
  26:                         select new FileInfo(x)).ToList();
  27:             }
  28:             catch (Exception e){
  29:                 Trace.WriteLine(e.Message);
  30:             }
  31:
  32:             return new List<FileInfo>();
  33:         }
  34:
  35:
  36:         /// <summary>
  37:         /// Gets the list of directories 
  38:         /// </summary>
  39:         /// <param name="directory">The Directory to get the files from</param>
  40:         /// <returns>Returns the List of directories info for this directory.
  41:         /// Return null if an exception is raised</returns>
  42:         public static IList<DirectoryInfo> GetChildDirectories(string directory)
  43:         {
  44:             try
  45:             {
  46:                 return (from x in Directory.GetDirectories(directory)
  47:                         select new DirectoryInfo(x)).ToList();
  48:             }
  49:             catch (Exception e)
  50:             {
  51:                 Trace.WriteLine(e.Message);
  52:             }
  53:
  54:             return new List<DirectoryInfo>();
  55:         }
  56:
  57:         /// <summary>
  58:         /// Gets the root directories of the system
  59:         /// </summary>
  60:         /// <returns>Return the list of root directories</returns>
  61:         public static IList<DriveInfo> GetRootDirectories()
  62:         {
  63:             return (from x in DriveInfo.GetDrives() select x).ToList();
  64:         }
  65:     }
  66: }

ViewModel Implementation

I have defined a custom class named DirInfo which contains the necessary information about a file/directory/logical drive in the system like name, path, type, size etc.

Thing to note in this class are two dependency properties named “IsExpanded” and “IsSelected”. These properties will be used when binding the DirInfo collection with the left tree view. IsExpanded tells the tree view that the current node in the tree should be in expanded state while IsSelected property tells the tree view that this node should be the selected node in the tree. To support the dependency properties DirInfo derives from DependencyObject class. Following code snipped shows the complete code this class.

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5: using System.Collections.ObjectModel;
   6: using System.IO;
   7: using FileExplorer.Properties;
   8: using System.Windows;
   9: using System.Collections;
  10:
  11:
  12: namespace FileExplorer.ViewModel
  13: {
  14:     /// <summary>
  15:     /// Enum to hold the Types of different file objects
  16:     /// </summary>
  17:     public enum ObjectType
  18:     {
  19:         MyComputer = 0,
  20:         DiskDrive = 1,
  21:         Directory = 2,
  22:         File = 3
  23:     }
  24:
  25:     /// <summary>
  26:     /// Class for containing the information about a Directory/File
  27:     /// </summary>
  28:     public class DirInfo : DependencyObject
  29:     {
  30:         #region // Public Properties
  31:         public string Name { get; set; }
  32:         public string Path { get; set; }
  33:         public string Root { get; set; }
  34:         public string Size { get; set; }
  35:         public string Ext { get; set; }
  36:         public int DirType { get; set; }
  37:         #endregion
  38:
  39:         #region // Dependency Properties
  40:         public static readonly DependencyProperty propertyChilds = DependencyProperty.Register("Childs", typeof(IList<DirInfo>), typeof(DirInfo));
  41:         public IList<DirInfo> SubDirectories
  42:         {
  43:             get { return (IList<DirInfo>)GetValue(propertyChilds); }
  44:             set { SetValue(propertyChilds, value); }
  45:         }
  46:
  47:         public static readonly DependencyProperty propertyIsExpanded = DependencyProperty.Register("IsExpanded", typeof(bool), typeof(DirInfo));
  48:         public bool IsExpanded
  49:         {
  50:             get { return (bool)GetValue(propertyIsExpanded); }
  51:             set { SetValue(propertyIsExpanded, value); }
  52:         }
  53:
  54:         public static readonly DependencyProperty propertyIsSelected = DependencyProperty.Register("IsSelected", typeof(bool), typeof(DirInfo));
  55:         public bool IsSelected
  56:         {
  57:             get { return (bool)GetValue(propertyIsSelected); }
  58:             set { SetValue(propertyIsSelected, value); }
  59:         }
  60:         #endregion
  61:
  62:         #region // .ctor(s)
  63:         public DirInfo()
  64:         {
  65:             SubDirectories = new List<DirInfo>();
  66:             SubDirectories.Add(new DirInfo("TempDir"));
  67:         }
  68:
  69:         public DirInfo(string directoryName)
  70:         {
  71:             Name = directoryName;
  72:         }
  73:
  74:         public DirInfo(DirectoryInfo dir)
  75:             : this()
  76:         {
  77:             Name = dir.Name;
  78:             Root = dir.Root.Name;
  79:             Path = dir.FullName;
  80:             DirType = (int)ObjectType.Directory;
  81:         }
  82:
  83:         public DirInfo(FileInfo fileobj)
  84:         {
  85:             Name = fileobj.Name;
  86:             Path = fileobj.FullName;
  87:             DirType = (int)ObjectType.File;
  88:             Size = (fileobj.Length / 1024).ToString() + " KB";
  89:             Ext = fileobj.Extension + " File";
  90:         }
  91:
  92:         public DirInfo(DriveInfo driveobj)
  93:             : this()
  94:         {
  95:             if (driveobj.Name.EndsWith(@"\"))
  96:                 Name = driveobj.Name.Substring(0, driveobj.Name.Length - 1);
  97:             else
  98:                 Name = driveobj.Name;
  99:
 100:             Path = driveobj.Name;
 101:             DirType = (int)ObjectType.DiskDrive;
 102:         }
 103:         #endregion
 104:     }
 105: }

Now lets start implementing the viewmodel for each of the views one by one.

Since all viewmodel will have properties which are change aware so these classes will have to implement INotifyPropertyChanged interface. To have this change notification at one place, I have created an abstract base class “ViewModelBase” for all the viewmodel. Following piece of code contains the implementation of this class.

   1: namespace FileExplorer.ViewModel
   2: {
   3:     public abstract class ViewModelBase : INotifyPropertyChanged
   4:     {
   5:         #region INotifyPropertyChanged Members
   6:
   7:         public event PropertyChangedEventHandler PropertyChanged;
   8:
   9:         /// <summary>
  10:         /// Raises the PropertyChanged event
  11:         /// </summary>
  12:         /// <param name="propertyName">The property name</param>
  13:         protected void OnPropertyChanged(string propertyName)
  14:         {
  15:             if (PropertyChanged != null)
  16:                 PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
  17:         }
  18:
  19:         #endregion
  20:
  21:         public ViewModelBase()
  22:         {
  23:         }
  24:     }
  25: }

Let’s start with the viewmodel for the main window view. I have created a class named “ExplorerWindowViewModel” to represent this view model. This class will be used as a datacontext for all the views. This class derives from ViewModelBase class. Following are some of the important members of this class.

  • CurrentDirectory: This is object of type DirInfo. This contains the currently selected item in the tree view.
  • ShowTreeView: This is a command that will be invoked to hide the left side tree view.
  • CurrentItems : Collection of DirInfo. Contains all the subdirectories and files of the CurrentDirectory
  • FileTreeVM: Object of FileTreeViewModel. Used to access members specific to the left pane view model.
  • DirViewVM: Object of DirectoryViewerViewModel. used to access members for the right side pane viewmodel

This class contains a method named “RefreshCurrentItems” which is called whenever the user changes the current directory either by selecting a node in tree view or going inside a directory through the right side pane. This method basically updates the CurrentItems property which is basically a collection of DirInfo objects, It contains the children of current directory both files and folders. Following is the code for this class.

   1: namespace FileExplorer.ViewModel
   2: {
   3:     public class ExplorerWindowViewModel : ViewModelBase
   4:     {
   5:         #region // Private Members
   6:         private DirInfo _currentDirectory;
   7:         private FileExplorerViewModel _fileTreeVM;
   8:         private DirectoryViewerViewModel _dirViewerVM;
   9:         private IList<DirInfo> _currentItems;
  10:         private bool _showDirectoryTree = true;
  11:         private ICommand _showTreeCommand;
  12:         #endregion
  13:
  14:         #region // .ctor
  15:         public ExplorerWindowViewModel()
  16:         {
  17:             FileTreeVM = new FileExplorerViewModel(this);
  18:             DirViewVM = new DirectoryViewerViewModel(this);
  19:             ShowTreeCommand = new RelayCommand(param => this.DirectoryTreeHideHandler());
  20:         }
  21:         #endregion
  22:
  23:         #region // Public Properties
  24:         /// <summary>
  25:         /// Name of the current directory user is in
  26:         /// </summary>
  27:         public DirInfo CurrentDirectory
  28:         {
  29:             get { return _currentDirectory; }
  30:             set
  31:             {
  32:                 _currentDirectory = value;
  33:                 RefreshCurrentItems();
  34:                 OnPropertyChanged("CurrentDirectory");
  35:             }
  36:         }
  37:
  38:         /// <summary>
  39:         /// Tree View model
  40:         /// </summary>
  41:         public FileExplorerViewModel FileTreeVM
  42:         {
  43:             get { return _fileTreeVM; }
  44:             set
  45:             {
  46:                 _fileTreeVM = value;
  47:                 OnPropertyChanged("FileTreeVM");
  48:             }
  49:         }
  50:
  51:
  52:         /// <summary>
  53:         /// Visibility of the 
  54:         /// </summary>
  55:         public bool ShowDirectoryTree
  56:         {
  57:             get { return _showDirectoryTree; }
  58:             set
  59:             {
  60:                 _showDirectoryTree = value;
  61:                 OnPropertyChanged("ShowDirectoryTree");
  62:             }
  63:         }
  64:
  65:
  66:         /// <summary>
  67:         /// 
  68:         /// </summary>
  69:         public ICommand ShowTreeCommand
  70:         {
  71:             get { return _showTreeCommand; }
  72:             set
  73:             {
  74:                 _showTreeCommand = value;
  75:                 OnPropertyChanged("ShowTreeCommand");
  76:             }
  77:         }
  78:
  79:         /// <summary>
  80:         /// Tree View model
  81:         /// </summary>
  82:         public DirectoryViewerViewModel DirViewVM
  83:         {
  84:             get { return _dirViewerVM; }
  85:             set
  86:             {
  87:                 _dirViewerVM = value;
  88:                 OnPropertyChanged("DirViewVM");
  89:             }
  90:         }
  91:
  92:         /// <summary>
  93:         /// Children of the current directory to show in the right pane
  94:         /// </summary>
  95:         public IList<DirInfo> CurrentItems
  96:         {
  97:             get
  98:             {
  99:                 if (_currentItems == null)
 100:                 {
 101:                     _currentItems = new List<DirInfo>();
 102:                 }
 103:                 return _currentItems;
 104:             }
 105:             set
 106:             {
 107:                 _currentItems = value;
 108:                 OnPropertyChanged("CurrentItems");
 109:             }
 110:         }
 111:         #endregion
 112:
 113:         #region // methods
 114:         private void DirectoryTreeHideHandler()
 115:         {
 116:             ShowDirectoryTree = false;
 117:         }
 118:
 119:         /// <summary>
 120:         /// this method gets the children of current directory and stores them in the CurrentItems Observable collection
 121:         /// </summary>
 122:         protected void RefreshCurrentItems()
 123:         {
 124:             IList<DirInfo> childDirList = new List<DirInfo>();
 125:             IList<DirInfo> childFileList = new List<DirInfo>();
 126:
 127:             //If current directory is "My computer" then get the all logical drives in the system
 128:             if (CurrentDirectory.Name.Equals(Resources.My_Computer_String))
 129:             {
 130:                 childDirList = (from rd in FileSystemExplorerService.GetRootDirectories()
 131:                                 select new DirInfo(rd)).ToList();
 132:             }
 133:             else
 134:             {
 135:                 //Combine all the subdirectories and files of the current directory
 136:                 childDirList = (from dir in FileSystemExplorerService.GetChildDirectories(CurrentDirectory.Path)
 137:                                 select new DirInfo(dir)).ToList();
 138:
 139:                 childFileList = (from fobj in FileSystemExplorerService.GetChildFiles(CurrentDirectory.Path)
 140:                                  select new DirInfo(fobj)).ToList();
 141:
 142:                 childDirList = childDirList.Concat(childFileList).ToList();
 143:             }
 144:
 145:             CurrentItems = childDirList;
 146:         }
 147:         #endregion
 148:     }
 149: }

Now lets talk about tree viewmodel. This is defined in a class named “FileExplorerViewModel”. This viewmodel contains a property SystemDirectorySource which is list of root nodes in the tree. Right now, I am adding only My Computer node to it. This class has a method ExpandToCurrentNode which sets the IsExpanded property of the CurrentItem to true if this is also the selected item in right side pane. Following is the code for this class.

   1:
   2: namespace FileExplorer.ViewModel
   3: {
   4:     public class FileExplorerViewModel : ViewModelBase
   5:     {
   6:         #region // Private fields
   7:         private ExplorerWindowViewModel _evm;
   8:         private DirInfo _currentTreeItem;
   9:         private IList<DirInfo> _sysDirSource;
  10:         #endregion
  11:
  12:         #region // Public properties
  13:         /// <summary>
  14:         /// list of the directories 
  15:         /// </summary>
  16:         public IList<DirInfo> SystemDirectorySource
  17:         {
  18:             get { return _sysDirSource; }
  19:             set
  20:             {
  21:                 _sysDirSource = value;
  22:                 OnPropertyChanged("SystemDirectorySource");
  23:             }
  24:         }
  25:
  26:         /// <summary>
  27:         /// Current selected item in the tree
  28:         /// </summary>
  29:         public DirInfo CurrentTreeItem
  30:         {
  31:             get { return _currentTreeItem; }
  32:             set
  33:             {
  34:                 _currentTreeItem = value;
  35:                 _evm.CurrentDirectory = _currentTreeItem;
  36:             }
  37:         }
  38:         #endregion
  39:
  40:         #region // .ctor
  41:         /// <summary>
  42:         /// ctor
  43:         /// </summary>
  44:         /// <param name="evm"></param>
  45:         public FileExplorerViewModel(ExplorerWindowViewModel evm)
  46:         {
  47:             _evm = evm;
  48:
  49:             //create a node for "my computer"
  50:             // this will be the root for the file system tree
  51:             DirInfo rootNode = new DirInfo(Resources.My_Computer_String);
  52:             rootNode.Path = Resources.My_Computer_String;
  53:             _evm.CurrentDirectory = rootNode; //make root node as the current directory
  54:
  55:             SystemDirectorySource = new List<DirInfo> { rootNode };
  56:         }
  57:         #endregion
  58:
  59:         #region // public methods
  60:         /// <summary>
  61:         /// 
  62:         /// </summary>
  63:         /// <param name="curDir"></param>
  64:         public void ExpandToCurrentNode(DirInfo curDir)
  65:         {
  66:             //expand the current selected node in tree 
  67:             //if this is an ancestor of the directory we want to navigate or "My Computer" current node 
  68:             if (CurrentTreeItem != null && (curDir.Path.Contains(CurrentTreeItem.Path) || CurrentTreeItem.Path == Resources.My_Computer_String))
  69:             {
  70:                 // expand the current node
  71:                 // If the current node is already expanded then first collapse it n then expand it
  72:                 CurrentTreeItem.IsExpanded = false;
  73:                 CurrentTreeItem.IsExpanded = true;
  74:             }
  75:         }
  76:         #endregion
  77:     }
  78: }

finally lets talk about viewmodel for right side pane. This viewmodel is defined in DirectoryViewerViewModel class. Main responsibility of this viewmodel is to keep track of the current directory user is moving to and processing the user event once user invokes the action for opening the currently selected item in the pane by either double clicking or by pressing keyboard Enter key.

I am maintaining the currently selected item in an property named CurrentItem. This is a simple C# property which the view will update accordingly as the selection in view changes. To handle the user event, I have a method named OpenCurrentObject(). This method depending on the object type performs the desired operation i.e. if this is a file then it uses System.Diagnostics.Process.Start method to open that file or if the current object is a directory then it shows its child and updates the tree view accordingly using the ExpandToCurrentNode method of the FileExplorerViewModel. Following piece of code contains the implementation for this class.

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5: using System.Windows;
   6: using System.Windows.Controls;
   7: using System.Windows.Input;
   8: using FileExplorer.Model;
   9:
  10: namespace FileExplorer.ViewModel
  11: {
  12:     /// <summary>
  13:     /// View model for the right side pane
  14:     /// </summary>
  15:     public class DirectoryViewerViewModel : ViewModelBase
  16:     {
  17:         #region // Private variables
  18:         private ExplorerWindowViewModel _evm;
  19:         private DirInfo _currentItem;
  20:         #endregion
  21:
  22:         #region // .ctor
  23:         public DirectoryViewerViewModel(ExplorerWindowViewModel evm)
  24:         {
  25:             _evm = evm;
  26:         }
  27:         #endregion
  28:
  29:         #region // Public members
  30:         /// <summary>
  31:         /// Indicates the current directory in the Directory view pane
  32:         /// </summary>
  33:         public DirInfo CurrentItem
  34:         {
  35:             get { return _currentItem; }
  36:             set { _currentItem = value; }
  37:         }
  38:         #endregion
  39:
  40:         #region // Public Methods
  41:         /// <summary>
  42:         /// processes the current object. If this is a file then open it or if it is a directory then return its subdirectories
  43:         /// </summary>
  44:         public void OpenCurrentObject()
  45:         {
  46:             int objType = CurrentItem.DirType; //Dir/File type
  47:
  48:             if ((ObjectType)CurrentItem.DirType == ObjectType.File)
  49:             {
  50:                 System.Diagnostics.Process.Start(CurrentItem.Path);
  51:             }
  52:             else
  53:             {
  54:                 _evm.CurrentDirectory = CurrentItem;
  55:                 _evm.FileTreeVM.ExpandToCurrentNode(_evm.CurrentDirectory);
  56:             }
  57:         }
  58:         #endregion
  59:     }
  60: }

View Implementation

Lets start with the implementation of view for directory tree in the left pane. Implementation for this is simple. I have a tree view whose itemsource property is set to SystemDirectorySource property of the FileTreeViewModel class. Then I have a HierarchicalDataTemplate whose target type is DirInfo so whenever an object of type DirInfo is bound to the tree this template will get applied to it. While binding to the DirInfo object I am using a converter which basically converts the DirInfo object into a List of DirInfo object whose content are the child directories of the directory represented by DirInfo object. Following code snippet contains code for this converter.

   1: public class GetFileSysemInformationConverter : IValueConverter
   2:    {
   3:        #region IValueConverter Members
   4:
   5:        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
   6:        {
   7:            try {
   8:                DirInfo nodeToExpand = value as DirInfo;
   9:                if (nodeToExpand == null)
  10:                    return null;
  11:
  12:                 //return the subdirectories of the Current Node
  13:                 if ((ObjectType)nodeToExpand.DirType == ObjectType.MyComputer)
  14:                 {
  15:                     return (from sd in FileSystemExplorerService.GetRootDirectories()
  16:                                     select new DirInfo(sd)).ToList();
  17:                 }
  18:                 else
  19:                 {
  20:                     return (from dirs in FileSystemExplorerService.GetChildDirectories(nodeToExpand.Path)
  21:                             select new DirInfo(dirs)).ToList();
  22:                 }
  23:
  24:            }
  25:            catch { return null; }
  26:        }
  27:
  28:        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  29:        {
  30:            throw new NotImplementedException();
  31:        }
  32:
  33:        #endregion
  34:    }

In this view I am also defining a style which applies to TreeViewItems. This style binds the IsExpanded and IsSelected properties of DirInfo object to a node in the tree. I am also assigning an event handler for the Expanded event of a node in tree. Following XAML code shows the hierarchical data template and Style for TreeViewItem.

   1: <local:GetFileSysemInformationConverter x:Key="getFileSysemInformationConverter"/>
   2:
   3:         <HierarchicalDataTemplate DataType="{x:Type local:DirInfo}"
   4:                                   ItemsSource="{Binding Converter={StaticResource getFileSysemInformationConverter}}">
   5:             <StackPanel Orientation="Horizontal">
   6:                 <Image Width="20" Height="20" Stretch="Fill" x:Name="img" />
   7:                 <TextBlock Text="{Binding Name}" Margin="5,0,0,0" />
   8:             </StackPanel>
   9:             <HierarchicalDataTemplate.Triggers>
  10:                 <DataTrigger Binding="{Binding Path=DirType}" Value="0">
  11:                     <Setter Property="Image.Source" TargetName="img" Value="/Images/MyComputer.jpg"></Setter>
  12:                 </DataTrigger>
  13:                 <DataTrigger Binding="{Binding Path=DirType}" Value="1">
  14:                     <Setter Property="Image.Source" TargetName="img" Value="/Images/diskdrive.png"></Setter>
  15:                 </DataTrigger>
  16:                 <DataTrigger Binding="{Binding Path=DirType}" Value="2">
  17:                     <Setter Property="Image.Source" TargetName="img" Value="/Images/folder.png"></Setter>
  18:                 </DataTrigger>
  19:             </HierarchicalDataTemplate.Triggers>
  20:         </HierarchicalDataTemplate>
  21:
  22:         <Style TargetType="TreeViewItem">
  23:             <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
  24:             <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
  25:             <EventSetter Event="Expanded" Handler="TreeView_Expanded"></EventSetter>
  26:         </Style>

While developing this I was facing issue in syncing the right side pane with the tree view. To fix this I used the TreeNode expanded method. In this method I check all the direct children of the node to search for the the child with the name equal to the CurrentDirectory value. If found, Expand this node. Following is the code for the Expanded method.

 

   1: private void TreeView_Expanded(object sender, RoutedEventArgs e)
   2:         {
   3:             TreeViewItem currentTreeNode = sender as TreeViewItem;
   4:             if (currentTreeNode == null)
   5:                 return;
   6:  
   7:             if (currentTreeNode.ItemsSource == null)
   8:                 return;
   9:  
  10:             DirInfo parentDirectory = currentTreeNode.Header as DirInfo;
  11:             if (parentDirectory == null)
  12:                 return;
  13:  
  14:             foreach (DirInfo d in currentTreeNode.ItemsSource)
  15:             {
  16:                 if (myViewModel.CurrentDirectory.Path.Equals(d.Path))
  17:                 {
  18:                     d.IsSelected = true;
  19:                     d.IsExpanded = true;
  20:                     break;
  21:                 }
  22:             }
  23:             e.Handled = true;
  24:         } 

To sync the currently selected node in the tree view with CurrentItem property of the the FileTreeViewModel, first I was planning to do a simple two way binding between the viewmodel property and the treeview’s SelectedItem property. But while doing this I came to know (I am still learning the concepts :)) that since SelectedItem is a readonly property so I can only read from it, can’t assign a binding to it even in the OneWayToSource mode. So I hooked to the selectedItemChanged event of the tree view and in the code behind I assigned the selectedItem property to the currentItem property as follows.

   1: private void DirectoryTree_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
   2: {
   3:      myViewModel.FileTreeVM.CurrentTreeItem = DirectoryTree.SelectedItem as DirInfo;
   4: }

Now lets see the implementation for right side pane i.e. where we show the children of a directory. I am using a Listbox to display the child directories and files. This list box’s ItemSource is set to the CurrentItems property of the ExplorerWindowViewModel. since we want to show the items of the list box wrapped according to the width of the explorer window so I am using WrapPanel control as the ItemsPanelTemplate for this listbox.

   1: <ListBox x:Name="dirList"  
   2:                  ItemsSource="{Binding Path=CurrentItems}" 
   3:                  ItemTemplate="{StaticResource DirViewTemplate}" BorderThickness="0" 
   4:                  HorizontalContentAlignment="Left" VerticalContentAlignment="Top" 
   5:                  Grid.Column="0" Grid.Row="0" 
   6:                  ScrollViewer.HorizontalScrollBarVisibility="Disabled"
   7:                  SelectedItem="{Binding Path=DirViewVM.CurrentItem,Mode=OneWayToSource}"
   8:                  MouseDoubleClick="dirList_MouseDoubleClick"
   9:                  KeyDown="dirList_KeyDown">
  10:             <ListBox.ItemsPanel>
  11:                 <ItemsPanelTemplate>
  12:                     <WrapPanel Orientation="Horizontal" ItemWidth="220"></WrapPanel>
  13:                 </ItemsPanelTemplate>
  14:             </ListBox.ItemsPanel>
  15:         </ListBox>

Each listboxitem represents a directory or a file. Each object will have a image, a name, type whether a FileFolder or a extention of that file and Size of the file. So I have defined an DataTemplate for each of the list box item as follows

   1: <!-- Data template for displaying a directory or a file -->
   2:         <DataTemplate x:Key="DirViewTemplate">
   3:             <Label HorizontalAlignment="Left" 
   4:                    Background="Transparent" 
   5:                    DataContext="{Binding}"
   6:                    IsTabStop="True" BorderThickness="1" >
   7:                 <Label.Content>
   8:                     <DockPanel>
   9:                         <Image DockPanel.Dock="Left" VerticalAlignment="Center"
  10:                                x:Name="img" 
  11:                                Margin="5" 
  12:                                Width="50" Height="50" />
  13:                         <StackPanel DockPanel.Dock="Left" 
  14:                                     VerticalAlignment="Center" HorizontalAlignment="Left" 
  15:                                     x:Name="ObjInfoPanel"> 
  16:                             <TextBlock x:Name="ObjName"
  17:                                        FontWeight="Bold" 
  18:                                        Text="{Binding Name}" 
  19:                                        Style="{StaticResource NormalTextBlockStyle}"/>
  20:                             <TextBlock x:Name="ObjType"
  21:                                        Style="{StaticResource FadedTextBlockStyle}"/>
  22:                             <TextBlock x:Name="ObjSize"
  23:                                        HorizontalAlignment="Left"
  24:                                        Style="{StaticResource FadedTextBlockStyle}"/>
  25:                         </StackPanel>
  26:                     </DockPanel>
  27:                 </Label.Content>
  28:                 <Label.ToolTip>
  29:                     <ToolTip Name="FileInfo" Placement="Mouse">
  30:                         <TextBlock Text="{Binding Name}"/>
  31:                     </ToolTip>
  32:                 </Label.ToolTip>
  33:             </Label>
  34:             
  35:              <DataTemplate.Triggers>
  36:                 <DataTrigger Binding="{Binding Path=DirType}" Value="0">
  37:                     <Setter Property="Image.Source" TargetName="img" Value="/Images/MyComputer.jpg"></Setter>
  38:                 </DataTrigger>
  39:                 <DataTrigger Binding="{Binding Path=DirType}" Value="1">
  40:                     <Setter Property="Image.Source" TargetName="img" Value="/Images/diskdrive.png"></Setter>
  41:                 </DataTrigger>
  42:                 <DataTrigger Binding="{Binding Path=DirType}" Value="2">
  43:                     <Setter Property="Image.Source" TargetName="img" Value="/Images/folder.png"></Setter>
  44:                     <Setter Property="Text" TargetName="ObjType" Value="File Folder"></Setter>
  45:                 </DataTrigger>
  46:                 <DataTrigger Binding="{Binding Path=DirType}" Value="3">
  47:                     <Setter Property="Image.Source" TargetName="img" Value="/Images/file.png"></Setter>
  48:                     <Setter Property="Text" TargetName="ObjType" Value="{Binding Ext}"></Setter>
  49:                     <Setter Property="Visibility" TargetName="ObjSize" Value="Visible"></Setter>
  50:                     <Setter Property="Text" TargetName="ObjSize" Value="{Binding Size}"></Setter>
  51:                 </DataTrigger>
  52:                  <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ListBoxItem}},Path=IsSelected}" Value="True" >
  53:                      <Setter Property="Background" TargetName="ObjInfoPanel" Value="Gray" ></Setter>
  54:                      <!--<Setter Property="Foreground" TargetName="ObjName" Value="Gray" ></Setter>-->
  55:                      <Setter Property="Foreground" TargetName="ObjType" Value="Black" ></Setter>
  56:                      <Setter Property="Foreground" TargetName="ObjSize" Value="Black" ></Setter>
  57:                 </DataTrigger>
  58:             </DataTemplate.Triggers>
  59:         </DataTemplate>    

 

Like the actual windows explorer, I also wanted to have the properties of a folder or a file to be shown in a different color like its type/ext or size in a dimmed or grayish color. Similarly, when file/folder name is larger then the width of the text block then trim it. So I defined two styles targeting the TextBlock control.

   1: <!-- Style for folder/file name text block -->
   2: <Style x:Key="NormalTextBlockStyle">
   3:             <!--<Setter Property="TextBlock.Width" Value="120"></Setter>-->
   4:             <Setter Property="TextBlock.TextWrapping" Value="NoWrap"></Setter>
   5:             <Setter Property="TextBlock.TextTrimming" Value="CharacterEllipsis"></Setter>
   6:             <Setter Property="TextBlock.VerticalAlignment" Value="Center"></Setter>
   7:         </Style>
   8:         
   9:         <!-- Style for folder/file type and size text block -->
  10:         <Style x:Key="FadedTextBlockStyle" BasedOn="{StaticResource NormalTextBlockStyle}">
  11:             <Setter Property="TextBlock.Foreground" Value="DimGray"></Setter>
  12:         </Style>

WPF provides an excellent support for trimming the content of a text block. It has a property TextTrimming which when set to value CharacterEllipsis along with TextWrapping set to value NoWrap , It automatically trims the content of the text box. All the text blocks in the style will have the same properties except the foreground color of the text. So I defined the style FadedTextBlockStyle which inherits the value for some styling from the NormalTextBlockStyle using the BasedOn attribute. This inheritance is similar to OOPS inheritance.

Now since we are using listbox for the item container, whenever an item is selected from the list it applies its default style to it i.e. it grays out the background of the list item. which is not what happens in windows file explorer, it grays only the folder name and its property text. So as every beginner would do, I wrote a style targeting the TreeViewItem which has a trigger waiting to fire whenever the tree view item is selected and when it fires it goes and changes the background of the treeviewitem to transparent. But it didn’t work. I should not actually since the background color of the selected items is controlled by HighlightBrush. So after little searching found out this style which sets the background of the treeviewitem to transparent.

   1: <Style TargetType="ListBoxItem">
   2:     <Style.Resources>
   3:          <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent"/>
   4:          <SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="Transparent"/>
   5:     </Style.Resources>
   6: </Style>

Now use these above two user controls to make outer window UI. This one is simple, It has a grid with three columns. First column has the treeview, second column has the gridsplitter and the third column has the directoryviewer view.

To set the dataContext right, I overridden the OnStartUp method of the App.XAML. In this method, I created an object of ExplorerWindowViewModel and assigned it as the data context for the Outer window.

   1: protected override void OnStartup(StartupEventArgs e)
   2:        {
   3:            base.OnStartup(e);
   4:  
   5:            ExplorerWindow window = new ExplorerWindow();
   6:  
   7:            // ViewModel to bind the main window 
   8:            ExplorerWindowViewModel viewModel = new ExplorerWindowViewModel();
   9:  
  10:            // Allow all controls in the window to bind to the ViewModel by setting the 
  11:            // DataContext, which propagates down the element tree.
  12:            window.DataContext = viewModel;
  13:  
  14:            window.Show();
  15:        }

 

Complete source code is available here. Please remove  .doc from file . This is due to restriction from wordpress to not allow zip files as uploads.

So this makes the complete file explorer with all the requirements we started with in start met. But still it has lots of scope for improvement.

Please drop in your suggestions. As I am still learning nuances of WPF. your comments might help me in my this quest.

Stay tuned for more WPF 101 coming …… :)

About these ads

40 Responses to “WPF 101 : A Simple Windows Explorer like Directory Browser”

  1. Danny said

    Hello!

    it’s so useful project.
    now, i’d like ask something to you.
    i wanna search tree node directly in code.

    but i can’t expand parent treenode. only can expend last node.
    can you give me some tips?

    i’m looking forward to answer to you.

  2. abhinav said

    Hello Manoj

    first of all thanks for this excellent control. I was just wondering of its possible to bind the tree view explorer root from Desktop shell as it is shown in Windows explorer

    can you please help me in it

    regards
    abhinav

  3. Ashish said

    Hi,
    The code download link doesnt work.

  4. Sam said

    Great Example! Thanks for posting it.

    Source code download link is broken, can you fix the link please?

    -Sam

  5. Skytop said

    The link to the source files is broken. Please update.

  6. Paul said

    WordPress has suppressed your zip file again. Do you have another link to your source file?

  7. gert said

    Great article! Is it possible to update the link to the source file because it seems to be broken. Thanks!

  8. Mostafa said

    Great work. But the download link still not working.

  9. Hi i like it
    Can you upload again because the link is broken.

  10. Clifi said

    The link is down

  11. Viggo Jensen - Denmark said

    Super informative article. Does anybody know how to download combined source?
    -Viggo

  12. Manoj Garg said

    Hi All,

    Somehow the link is not working..everytime I upload the file… wordpress is removing it… ne idea how to uplaod it…

  13. Viggo Jensen - Denmark said

    I sometimes use Dropbox.com to distribute files however for a short periode of time. It’s free for small amounts of memory.
    Check this url: http://dl.dropbox.com/u/22418793/Foto/Felix.JPG
    or this: http://dl.dropbox.com/u/22418793/Foto/mdac_typ.exe (do not run file, just a check)
    or this: http://dl.dropbox.com/u/22418793/Foto/Hot_chick_with_nice_pussy_1.jpg
    /Viggo

  14. Viggo Jensen - Denmark said

    Hi Manoj Garg and others

    I often use Dropbox.com. It’s free and easy to use. Please try:
    http://dl.dropbox.com/u/22418793/Foto/Felix.JPG
    http://dl.dropbox.com/u/22418793/Foto/Hot_chick_with_nice_pussy_1.jpg (just fun).
    /Viggo

  15. Viggo Jensen - Denmark said

    Hi all

    I often use Dropbox.com. it’s free and simple to use. Try this:
    http://dl.dropbox.com/u/22418793/Foto/Felix.JPG (my dog)
    http://dl.dropbox.com/u/22418793/Foto/Hot_chick_with_nice_pussy_1.jpg (funny picture)

    /Viggo

  16. Prashant said

    Hi Manoj,
    Thanks for this excellent article. Do you any other link to download the source code. Above download link is not working properly.

    -Prashant

  17. Prashant said

    Hi Manoj,

    Can you please upload the source code again .

    Thanks

  18. chirila alexandra said

    hello,

    i have a homework assignment to do and i could really use your help…can you please e-mail me the source files for this project?? my e-mail address is: chirila.alexandra@yahoo.com

    please help :(

  19. giaosudau said

    @Manoj: Can you upload your source code via http://www.mediafire.com or hotfile.com any uploading service it’s free. Thank you:)

  20. Alex said

    Nice article..

    Could you fix the link??

    upload it on sendspace or windows live or something hehe :)

  21. Kunal said

    Great information Manoj. I implemented everything but View and need some help with it. Can you email me the solution. I understand that you can’t put this on wordpress.
    Thanks Manoj

  22. Sviatoslav said

    Hi, this link doesnt work.

    Where i can download this source code?

  23. Brent Reinhart said

    The download link doesn’t work, could you send me the file?

    thanks,
    Brent

  24. Mike said

    You can create an explorer…from scratch…yet you can’t work out how to upload a file. Priceless. :D

    Anyway, this is a brilliant effort, and thanks for posting it. Maybe you could upload your solution to megaupload or MediaFire, and then just post the link here.

  25. George said

    Great article,
    But the link to source code is broken. Did you publish it anywhere else?

  26. Ken said

    Hi Manoj Garg, your post is real good and helpful, would you mind to send your code by email: ken_cz@live.cn . Thank you very much!

  27. Robert Di Angelo said

    Hey,

    The article is very good, but source code would be great help for everyone, I think. Beacaude of that, maybe you should host file withe the source on another server, like eg. hotfile.com or sendspace.com.

    Please, think about it;)

    Best wishes,
    Robert

  28. wesaday said

    http://en.support.wordpress.com/uploading-documents/

  29. Denis said

    Create your project on Codeplex and upload it on it. Why not ? It is very usefull project.

  30. Chris said

    Hi

    could you try to upload your file? I suggest to remove “zip” as a substring and also set a simple password. Maybe use “txt” as an extension.

    Thanks

  31. Manoj Garg said

    Hi guys,

    really sorry for a late reply. I have managed to put the file on Dropbox (thanks for suggesting it)

    http://dl.dropbox.com/u/34591870/FileExplorer_final.zip

    try accessing the URL and let me know if you face any issues.

    Thanks,
    Manoj

  32. Busines Directory in Chicago…

    [...]WPF 101 : A Simple Windows Explorer like Directory Browser « Me Myself & C#[...]…

  33. Rajesh Kumar said

    Hi Manoj,

    Your post is real good and helpful.

    Rajesh Kumar

  34. MSP Fabrice Jean-Cédric HAUHOUOT said

    Thanks you ! Manoj i’m working on same application now ! It’s also good you are upload now you code source !

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
Follow

Get every new post delivered to your Inbox.

Join 410 other followers

%d bloggers like this: