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

Requirements:
-
It should have a left tree showing the directory tree rooted at “My Computer”
-
It should have a right pane showing all the children of the current node selected in the left side tree.
-
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.
-
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

View:
we will be need following views:
-
View for Left Tree : File “View\FileSystemTree.xaml” contains this view.
-
View for Right Pane : File “View\DirectoryViewer.xaml” contains this view.
-
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.
-
ViewModelBase class: This base class is responsible for handling the notification mechanism. It implements INotifyPropertyChanged interface. This is an abstract class.
-
FileExplorerViewModel Class: this class is the Viewmodel for the left side tree.
-
DirectoryViewerViewModel Class: This class is the viewmodel for the right side pane.
-
ExplorerWindowViewModel Class: This class is the View model for the overall outer window.
-
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.

Implementation
Model Implementation
Following image shows the directory structure for the file explorer.

Lets start with the code for Model. Our model has just one class named “FileSystemExplorerService” which has three methods
-
GetRootDirectories : This method returns the logical drives in the local file system.
-
GetChildDirectories : This method returns the sub directories of the directory path passed.
-
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 …… 🙂