Introduction
As you may know, I have always been very curious about how things work. Being a programming teacher also helps because I get a lot of questions from students, and it is always a joy; OK, more like a challenge to work out a simple project based on their questions.
Today, I will cover one of the most popular questions in my classroom, as well as on the forums. The topic of today is how to create an application similar to Windows Explorer. Just a note, this program used to be an FAQ on CodeGuru forums, but it is over six years old already and it is indeed time for a proper facelift and proper explanation on the logic involved.
Design
Open Visual Studio 2012 and create a new Windows Forms Application in either C# or VB.NET. Put the following controls ( with their Properties ) on your Form:
Control | Property | Value |
Form | Name | frmWinExplore |
TreeView | Name | trvFolders |
ContextMenuStrip | cmClipboardOp | |
ListView | Name | lvFiles |
ContextMenuStrip | cmClipboardOp | |
ComboBox | Name | cboView |
Items | Large Icon Details Small Icon List Tile |
|
Button | Name | btnExit |
Text | Exit | |
ImageList | Name | iIconList |
ContextMenuStrip | Name | cmClipboardOp |
Copy | ||
Paste |
Code
The most logical place to start is with getting all the folders / directories.
Folders
Because we are working with the file system here, we need to include our System.IO namespace:
VB.NET
Imports System.IO 'File Operations
C#
using System.IO; //File Operations
Now, we need the “magic” code to get all system folders – let us add it now:
VB.NET
Private Sub AddAllFolders(ByVal TNode As TreeNode, ByVal FolderPath As String) Try For Each FolderNode As String In Directory.GetDirectories(FolderPath) 'Load All Sub Folders Dim SubFolderNode As TreeNode = TNode.Nodes.Add(FolderNode.Substring(FolderNode.LastIndexOf(""c) + 1)) 'Add Each Sub Folder Name SubFolderNode.Tag = FolderNode 'Set Tag For Each Sub Folder SubFolderNode.Nodes.Add("Loading...") Next Catch ex As Exception MessageBox.Show(ex.Message) 'Something Went Wrong End Try End Sub
C#
private void AddAllFolders(TreeNode TNode, string FolderPath) { try { foreach (string FolderNode in Directory.GetDirectories(FolderPath)) //Load All Sub Folders { TreeNode SubFolderNode = TNode.Nodes.Add(FolderNode.Substring(FolderNode.LastIndexOf('\') + 1)); //Add Each Sub Folder Name SubFolderNode.Tag = FolderNode; //Set Tag For Each Sub Folder SubFolderNode.Nodes.Add("Loading..."); } } catch (Exception ex) //Something Went Wrong { MessageBox.Show(ex.Message); } }
This sub obtains all the sub folders inside the supplied path. It then iterates through them and adds it to the TreeView. very important here, is that we add a Tag for each of the subfolders. This tag will actually represent each sub folder name, which we could use to obtain all the files inside the particular folder.
We now need to make use of the TreeView’s BeforeExpand method to get the subfolders of the selected folder inside the treeview:
VB.NET
Private Sub trvFolders_BeforeExpand(ByVal sender As Object, ByVal e As System.Windows.Forms.TreeViewCancelEventArgs) Handles trvFolders.BeforeExpand If e.Node.Nodes.Count = 1 AndAlso e.Node.Nodes(0).Text = "Loading..." Then e.Node.Nodes.Clear() 'Clear All Items AddAllFolders(e.Node, CStr(e.Node.Tag)) 'Add All Folders End If End Sub
C#
private void trvFolders_BeforeExpand(object sender, TreeViewCancelEventArgs e) { if (e.Node.Nodes.Count == 1 && e.Node.Nodes[0].Text == "Loading...") { e.Node.Nodes.Clear(); //Clear All Items AddAllFolders(e.Node, Convert.ToString(e.Node.Tag)); //Add All Folders } }
We clear all the previous nodes, and add all the subfolders of the selected TreeView folder via the AddAllFolders sub. The final piece of the puzzle for our folders is to add the initial starting node for our TreeView. This should be the drive you want to browse through. Add the next inside Form_Load:
VB.NET
Private Sub frmWinExplore_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load trvFolders.Sort() 'Sort Alphabetically Dim Tnode As TreeNode = trvFolders.Nodes.Add("(Drive C:)") 'Add Main Node AddAllFolders(Tnode, "C:") 'Add All Folders End Sub
C#
private void frmWinExplore_Load(object sender, EventArgs e) { trvFolders.Sort(); //Sort Alphabetically TreeNode Tnode = trvFolders.Nodes.Add("(Drive C:)"); //Add Main Node AddAllFolders(Tnode, "C:\"); //Add All Folders }
We Sort the TreeView alphabetically and add the main node to it. In this case it is Drive C:. We then call the AddAllFolders sub to add all the folders found on the C: drive.
If you were to run your project now, you’d be able to load all folders and their subfolders into the Treeview. Now, we need to obtain all the files inside the folders.
Files
Because we need a list of the files present in the selected folder, the most obvious place to get them is inside the TreeView’s AfterSelect method. This event happens when a selection was made in the TreeView. Let us add it now:
VB.NET
Private Sub trvFolders_AfterSelect(ByVal sender As Object, ByVal e As System.Windows.Forms.TreeViewEventArgs) Handles trvFolders.AfterSelect Dim FileExtension As String 'Stores File Extension Dim SubItemIndex As Integer 'Sub Item Counter Dim DateMod As String 'Stores Date Modified Of File lvFiles.Items.Clear() 'Clear Existing Items If trvFolders.SelectedNode.Nodes.Count = 1 AndAlso trvFolders.SelectedNode.Nodes(0).Text = "Loading..." Then trvFolders.SelectedNode.Nodes.Clear() 'Reset AddAllFolders(trvFolders.SelectedNode, CStr(trvFolders.SelectedNode.Tag)) End If Dim folder As String = CStr(trvFolders.SelectedNode.Tag) 'Folder Name If Not folder Is Nothing AndAlso Directory.Exists(folder) Then Try For Each file As String In Directory.GetFiles(folder) 'Get Files In Folder FileExtension = Path.GetExtension(file) 'Get File Extension(s) DateMod = IO.File.GetLastWriteTime(file).ToString() 'Get Date Modified For Each File AddImages(file) 'Add Icons lvFiles.Items.Add(file.Substring(file.LastIndexOf(""c) + 1), file.ToString()) 'Add Files & File Properties To ListView lvFiles.Items(SubItemIndex).SubItems.Add(FileExtension.ToString() & " File") lvFiles.Items(SubItemIndex).SubItems.Add(DateMod.ToString()) SubItemIndex += 1 Next Catch ex As Exception 'Something Went Wrong MessageBox.Show(ex.Message) End Try End If End Sub
C#
private void trvFolders_AfterSelect(object sender, TreeViewEventArgs e) { string FileExtension = null; //Stores File Extension int SubItemIndex = 0; //Sub Item Counter string DateMod = null; //Stores Date Modified Of File lvFiles.Items.Clear(); //Clear Existing Items if (trvFolders.SelectedNode.Nodes.Count == 1 && trvFolders.SelectedNode.Nodes[0].Text == "Loading...") { trvFolders.SelectedNode.Nodes.Clear(); //Reset AddAllFolders(trvFolders.SelectedNode, Convert.ToString(trvFolders.SelectedNode.Tag)); } string folder = Convert.ToString(trvFolders.SelectedNode.Tag); //Folder Name if ((folder != null) && System.IO.Directory.Exists(folder)) { try { foreach (string file in System.IO.Directory.GetFiles(folder)) //Get Files In Folder { FileExtension = System.IO.Path.GetExtension(file); //Get File Extension(s) DateMod = System.IO.File.GetLastWriteTime(file).ToString(); //Get Date Modified For Each File AddImages(file); //Add File Icons lvFiles.Items.Add(file.Substring(file.LastIndexOf('\') + 1), file.ToString()); //Add Files & File Properties To ListView lvFiles.Items[SubItemIndex].SubItems.Add(FileExtension.ToString() + " File"); lvFiles.Items[SubItemIndex].SubItems.Add(DateMod.ToString()); SubItemIndex += 1; } } catch (Exception ex) { MessageBox.Show(ex.Message); //Something Went Wrong } } }
We obtain the folder name, and ensure that it does in fact exist. It may sound silly to mention because with the AddAllFolders sub we added all the folders that exist, but you never know. A folder could have been deleted whilst looking for the files. A folder could have become corrupted. A folder could have moved.
We then get each file’s extension to identify the type of file it is. We will used the associated icons for each file. If you do not know what File Association is, have a look here. We get the Date Modified property for each file and add all of this info to our ListView.
You might have noticed that we made reference to a procedure called AddImages. We will add that shortly.
Before we proceed with the file icons, we have one little modification we need to do for our Form_Load event. Add the next few lines into Form_Load:
VB.NET
lvFiles.View = View.Details 'Set View of ListView ' Add ListView Columns With Specified Width lvFiles.Columns.Add("File Name", 150, HorizontalAlignment.Left) lvFiles.Columns.Add("File Type", 80, HorizontalAlignment.Left) lvFiles.Columns.Add("Date Modified", 150, HorizontalAlignment.Left)
C#
lvFiles.View = View.Details; //Set ListView View Option //Add ListView Columns With Specified Width lvFiles.Columns.Add("File Name", 150, HorizontalAlignment.Left); lvFiles.Columns.Add("File Type", 80, HorizontalAlignment.Left); lvFiles.Columns.Add("Date Modified", 150, HorizontalAlignment.Left);
This creates the necessary columns for our ListView.
File Icons
To be able to get each associated file’s icon, we need to make use of the SHGetFileInfo API function. This API obtains all info about files such as which icons are associated with it, to name only one. If you do not know what APIs are, look here, or file association look here.
The first thing to do is to add the appropriate namespace so that we can use APIs properly:
VB.NET
Imports System.Runtime.InteropServices 'APIs
C#
using System.Runtime.InteropServices; //APIs
Now we must create the API:
VB.NET
Private Structure SHFILEINFO 'Contains information about a file object Public hIcon As IntPtr 'Icon Public iIcon As Integer 'Icondex Public dwAttributes As Integer 'SFGAO_ flags <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=260)> _ Public szDisplayName As String <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=80)> _ Public szTypeName As String End Structure Private Declare Auto Function SHGetFileInfo Lib "shell32.dll" _ (ByVal pszPath As String, _ ByVal dwFileAttributes As Integer, _ ByRef psfi As SHFILEINFO, _ ByVal cbFileInfo As Integer, _ ByVal uFlags As Integer) As IntPtr 'Retrieves information about an object in the file system, such as a file, folder, directory, or drive root Private Const SHGFI_ICON = &H100 'Icon Private Const SHGFI_SMALLICON = &H1 'Small Icon Private Const SHGFI_LARGEICON = &H0 ' Large icon Private Const MAX_PATH = 260 'Path to Icon
C#
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct SHFILEINFO //Contains information about a file object { public IntPtr hIcon; //Icon public int iIcon; //Icondex public uint dwAttributes; //SFGAO_ flags [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string szDisplayName; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)] public string szTypeName; } [DllImport("shell32.dll")] //Retrieves information about an object in the file system, such as a file, folder, directory, or drive root public static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags); private const uint SHGFI_ICON = 0x100; //Icon private const uint SHGFI_SMALLICON = 0x1; //Small Icon private const uint SHGFI_LARGEICON = 0x0; // Large icon private const int MAX_PATH = 260; //Path to Icon
Let us now add the AddImages procedure to link each file with its associated icon:
VB.NET
Private Sub AddImages(ByVal strFileName As String) Try Dim shInfo As SHFILEINFO 'Create File Info Object shInfo = New SHFILEINFO() 'Instantiate File Info Object shInfo.szDisplayName = New String(vbNullChar, MAX_PATH) 'Get Display Name shInfo.szTypeName = New String(vbNullChar, 80) 'Get File Type Dim hIcon As IntPtr 'Get File Type Icon Based On File Association hIcon = SHGetFileInfo(strFileName, 0, shInfo, Marshal.SizeOf(shInfo), SHGFI_ICON Or SHGFI_SMALLICON) Dim MyIcon As Drawing.Bitmap 'Create icon MyIcon = Drawing.Icon.FromHandle(shInfo.hIcon).ToBitmap 'Set Icon iIconList.Images.Add(strFileName.ToString(), MyIcon) 'Add To ListView FileNames Catch ex As Exception MessageBox.Show(ex.Message & ex.Source) End Try End Sub
C#
private void AddImages(string strFileName) { char NullChar = Convert.ToChar(0); //NULL Character, C# Doesn't Have vbNullChar Constant Built In SHFILEINFO shInfo = default(SHFILEINFO); //Create File Info Object shInfo = new SHFILEINFO(); //Instantiate File Info Object shInfo.szDisplayName = new string(NullChar, MAX_PATH); //Get Display Name shInfo.szTypeName = new string(NullChar, 80); //Get File Type IntPtr hIcon = default(IntPtr); //Get File Type Icon Based On File Association hIcon = SHGetFileInfo(strFileName, 0, ref shInfo, (uint)Marshal.SizeOf(shInfo), SHGFI_ICON | SHGFI_SMALLICON); System.Drawing.Bitmap MyIcon = null; //Create Icon MyIcon = System.Drawing.Icon.FromHandle(shInfo.hIcon).ToBitmap(); //Set Icon ilFileIcons.Images.Add(strFileName.ToString(), MyIcon); //Add To ListView FileNames }
Copying and Pasting
Add the following code behind your Copy menustrip item’s code:
VB.NET
Private Sub CopyToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles CopyToolStripMenuItem.Click Dim lvItem As ListViewItem 'ListView item Dim lvSel As ListView.SelectedListViewItemCollection = Me.lvFiles.SelectedItems 'ListViewItems Dim strFileName As String 'File Name For Each lvItem In lvSel 'Loop Through Select File Names In ListView strFileName = trvFolders.SelectedNode.Tag & "" & lvItem.Text 'Get Selected File Name Dim clpDataObj As New DataObject() 'Create New Data Object Dim cbClipBoardFile(0) As String 'Break File Apart Into A String Array cbClipBoardFile(0) = strFileName clpDataObj.SetData(DataFormats.FileDrop, True, cbClipBoardFile) 'Put String Array Onto ClipBoard Clipboard.SetDataObject(clpDataObj) MessageBox.Show("File Copied To Clipboard") 'Inform User Next End Sub
C#
private void copyToolStripMenuItem_Click(object sender, EventArgs e) { ListView.SelectedListViewItemCollection lvSel = this.lvFiles.SelectedItems; //ListViewItems string strFileName = null; //File Name foreach ( ListViewItem lvItem in lvSel) //Loop Through All ListView Items { strFileName = trvFolders.SelectedNode.Tag + "\" + lvItem.Text; //Get Selected Filename DataObject clpDataObj = new DataObject(); //Create New DataObject string[] cbClipBoardFile = new string[1]; //Break File Apart Into A String Array cbClipBoardFile[0] = strFileName; clpDataObj.SetData(DataFormats.FileDrop, true, cbClipBoardFile); //Put String Array Onto ClipBoard Clipboard.SetDataObject(clpDataObj); MessageBox.Show("File Copied To Clipboard"); //Inform The User } }
We obtain the selected ListView item and concatenate it with the folder name (which is stored in that particular node’s tag). This gives us the complete file name and location. We break the file apart into a string array and place this string array onto the Clipboard. The file is now copied onto the clipboard.
Add the code to paste the file:
VB.NET
Private Sub PasteToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles PasteToolStripMenuItem.Click Dim idClipboardData As IDataObject = Clipboard.GetDataObject() 'Get Data Present on ClipBoard If idClipboardData.GetDataPresent(DataFormats.FileDrop) Then Dim strClipFile As String() = DirectCast(idClipboardData.GetData(DataFormats.FileDrop), String()) 'Convert String Array Back Into File Dim i As Integer For i = 0 To strClipFile.Length - 1 'If File Exists, Rename COpied File If File.Exists(trvFolders.SelectedNode.Tag & "" & Path.GetFileName(strClipFile(i))) Then File.Move(trvFolders.SelectedNode.Tag & "" & Path.GetFileName(strClipFile(i)), trvFolders.SelectedNode.Tag & "temp") End If 'Copy File From ClipbBoard File.Copy(strClipFile(i), trvFolders.SelectedNode.Tag & "" & Path.GetFileName(strClipFile(i))) Next MessageBox.Show("File Pasted From Clipboard") 'Inform User End If End Sub
C#
private void pasteToolStripMenuItem_Click(object sender, EventArgs e) { IDataObject idClipboardData = Clipboard.GetDataObject(); //Get Data Present on ClipBoard if (idClipboardData.GetDataPresent(DataFormats.FileDrop)) { string[] strClipFile = (string[])idClipboardData.GetData(DataFormats.FileDrop); //Convert String Array Back Into File int i = 0; for (i = 0; i <= strClipFile.Length - 1; i++) { //If File Exists, Rename COpied File if (File.Exists(trvFolders.SelectedNode.Tag + "\" + Path.GetFileName(strClipFile[i]))) { File.Move(trvFolders.SelectedNode.Tag + "\" + Path.GetFileName(strClipFile[i]), trvFolders.SelectedNode.Tag + "temp"); } //Copy File From ClipbBoard File.Copy(strClipFile[i], trvFolders.SelectedNode.Tag + "\" + Path.GetFileName(strClipFile[i])); } MessageBox.Show("File Pasted From Clipboard"); //Inform User } }
We identify the data on the clipboard. If the data resembles a file we then convert the string array back into a file. We attempt to paste it in the folder we selected in the TreeView. If the file already exists it will still copy it but change its name. If the file doesn’t exist yet, it will paste it in there.
Finishing up
All that is left to do is to program our View Combobox to be able to change Views and add code to our Exit button. Let us do that now:
VB.NET
Private Sub cboView_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cboView.SelectedIndexChanged Dim strSelView = CType(cboView.SelectedItem, String) 'Change ListView ViewMode Select Case strSelView Case "Large Icon" lvFiles.View = View.LargeIcon Case "Details" lvFiles.View = View.Details Case "Small Icon" lvFiles.View = View.SmallIcon Case "List" lvFiles.View = View.List Case "Tile" lvFiles.View = View.Tile End Select End Sub Private Sub btnExit_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExit.Click Application.Exit() 'Exit End Sub
C#
private void cboView_SelectedIndexChanged(object sender, EventArgs e) { string strSelView = Convert.ToString(cboView.SelectedItem); //Change ListView ViewMode switch (strSelView) { case "Large Icon": lvFiles.View = View.LargeIcon; break; case "Details": lvFiles.View = View.Details; break; case "Small Icon": lvFiles.View = View.SmallIcon; break; case "List": lvFiles.View = View.List; break; case "Tile": lvFiles.View = View.Tile; break; } } private void btnExit_Click(object sender, EventArgs e) { Application.Exit(); //Exit }
I am attaching both projects below, just in case you did something wrong.
Conclusion
That wasn’t too complicated, was it? Thanks for reading my article and I hope you have benefited from it. Until next time, Cheers!