And here, we are expecting that you already read the previous blog. Now, you're ready to go in this blog.
Actual Wrapper Implementation
Below you can see the actual implementation of the FileSystemObjectInfo wrapper class that we will going to use in our application.
public class FileSystemObjectInfo : BaseObject
{
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);
}
public FileSystemObjectInfo(DriveInfo
drive)
: this(drive.RootDirectory)
{
this.Drive = drive;
}
#region
Properties
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);
}
}
#endregion
#region
Methods
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());
}
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;*/
}
}
#endregion
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 = Shell.FolderManager.GetImageSource(this.FileSystemInfo.FullName, ItemState.Open);
if (this.HasDummy())
{
this.RemoveDummy();
this.ExploreDirectories();
this.ExploreFiles();
}
}
else
{
this.ImageSource = Shell.FolderManager.GetImageSource(this.FileSystemInfo.FullName, ItemState.Close);
}
}
}
}
private class DummyFileSystemObjectInfo : FileSystemObjectInfo
{
public DummyFileSystemObjectInfo()
:
base(new DirectoryInfo("DummyFileSystemObjectInfo"))
{
}
}
}
Now, let's go the XAML and WPF stuff bindings.
We need to create a new Window object in our solution and add a new TreeView inside it. See below the code.
<Window x:Class="CodesDirectory.WIN_TreeViewWithIcon"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:classes="clr-namespace:CodesDirectory.Classes"
Title="WIN TreeView with System Icons" Height="300" Width="300">
<TreeView Name="treeView" Margin="5"></TreeView>
</Window>
We also need to override the default style of the items. In this case the ItemContainerStyle value should be modified. But unlike with other Style we only bind the IsExpanded property of the TreeViewItem into the IsExpanded property of FileSystemObjectInfo class (two-way direction). So every user action in the TreeViewItem state will also be applied in the bound objects. See below our new code.
<Window x:Class="CodesDirectory.WIN_TreeViewWithIcon"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:classes="clr-namespace:CodesDirectory.Classes"
Title="WIN TreeView with System Icons" Height="300" Width="300">
<TreeView Name="treeView" Margin="5">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="KeyboardNavigation.AcceptsReturn" Value="True" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Window>
After that in the Resources property of the TreeView object. We have to use the HierarchicalDataTemplate object and targets the actual FileSystemObjectInfo class in the DataType property. With the use of this object, we can set the actual template that the TreeViewItem has participated. In our case, we need to create a template where there is an Image in the left and Label in the right. The image will do display the actual icon of the file system, it binds the ImageSource property of the FileSystemObjectInfo class, and the Label will be bind in the Name property of the FileSystemInfo (of type System.IO.FileSystemInfo) property of the FileSystemObjectInfo class. See below our actual codes now.
<Window x:Class="CodesDirectory.WIN_TreeViewWithIcon"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:classes="clr-namespace:CodesDirectory.Classes"
Title="WIN TreeView with System Icons" Height="300" Width="300">
<TreeView Name="treeView" Margin="5">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="KeyboardNavigation.AcceptsReturn" Value="True" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type classes:FileSystemObjectInfo}" ItemsSource="{Binding Path=Children}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=ImageSource, UpdateSourceTrigger=PropertyChanged}" Margin="0,1,8,1"></Image>
<TextBlock Text="{Binding Path=FileSystemInfo.Name}"></TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Window>
Lastly, in the code behind, we need to explore the top level drives and add each drive in the FileSystemObjectInfo wrapper class and append it to the TreeView.Items property. See below the codes on how to do it.
public partial class WIN_TreeViewWithIcon
: Window
{
public WIN_TreeViewWithIcon()
{
InitializeComponent();
var drives = DriveInfo.GetDrives();
foreach (var drive in drives)
{
this.treeView.Items.Add(new
FileSystemObjectInfo(drive));
}
}
}
And, congratulations to you for finishing this blog. You are now equipped with a new interesting programming technique called Shell.
Please follow us and be part of this blog site for the more very interesting stuff.
Could you please post the full project code for download?
ReplyDeleteThanks a lot.
Useless without Project to download... what a shame, your solution looked quite interesting
ReplyDeleteThank you for this tutorial. It was very informative and it eventually worked beautifully.
ReplyDeleteHaving the source code to download would have been extremely helpful, primarily because this project required so many namespaces not mentioned in your post and it was very time consuming hunting them down.
Just wanted to say "THANK YOU" :) works like a charm!
ReplyDeleteIt realy looks great and is well designed.
I really enjoyed with this lesson.
ReplyDeleteBig like :)
Thanks.
thanks for the sample, gave me a quick jump start on the topic and my project
ReplyDeleteI sincerely apologize for not being active here. I have moved my blogging to Medium and Twitter. Though, I tried my best to accommodate you on here.
ReplyDeleteHere is the link to the working project:
https://github.com/mikependon/Tutorials/tree/master/WPF/TreeViewFileExplorer
Moving forward, I will place everything in my Github account.
Here is the new medium tutorial.
ReplyDeletehttps://medium.com/@mikependon/designing-a-wpf-treeview-file-explorer-565a3f13f6f2