Introduction
This is a File Manager that operates similar to the Windows Explorer. It is a Windows Form control based DLL for portability, and it gives you the ability to have a file browser that is ready to drop into any application. Most of this stuff can be found spread across this site and others, but I thought it world be great for it to all be in one place with a working project.
Using a TreeView
component and a specified root directory, a file tree is created from the contents of the root directory. Using an ImageList
and a custom IconHelper
class, all nodes are assigned user specified images based on the folder or file type.
And since drag and drop is a must in any file explorer, it has integrated drag and drop functionality that works with both the file manager itself and with Windows Explorer. The user can drag and drop tree nodes within the file manager itself, and files can be dragged in from Windows Explorer to the file manager. No functionality for dragging from the file explorer to outside applications or Windows Explorer is implemented, but the functionality can be easily added. For larger file trees, scrolling of the control while dragging is also implemented.
Also, there are context menus for cut, copy, paste, delete, and folder creation. From inside the file explorer, the user can cut, copy, paste, delete, and create folders.
Last but not least, there is full Clipboard support. The file manager registers as a Clipboard viewer, and watches for the cut and copy clipboard events (no paste events, as they do not exist). When a user cuts or copies a file or folder from outside the file manager, the paste command in the file manager imports the data from the Clipboard.
Background
Currently, I am making an XNA based engine, and needed a way to organize and visually display all the files being used by the current project, so I figured I would just make a simple file browser to handle my file placement needs. This turned out to be more involved than I had thought. Over a month later, this is what I came up with. The next step is to integrate running the files inserted in custom build events to get the .XNB files needed by the XNA Framework to use the assets when inserted or added. Maybe once that step is done, I will upload that as well. I got a lot of help from this site while working on this, and I felt I needed to give something back for all the help. A lot of this code is spread out over this site and the Internet, but I figured it would be nice to have it all in one place.
Using the code
File tree
Let's start with the most basic part of the file manager, the tree view. Using a tree view component and the file browser class, the EnumerateDirectory
function populates the tree view using the specified root path:
public void EnumerateDirectory(TreeNode parentNode)
{
try
{
DirectoryInfo diRootInfo;
diRootInfo = new DirectoryInfo(parentNode.FullPath + "\\");
foreach (DirectoryInfo dir in diRootInfo.GetDirectories())
{
TreeNode node = new TreeNode();
node.Text = dir.Name;
node.ImageIndex = 0;
node.SelectedImageIndex = 0;
parentNode.Nodes.Add(node);
if (HasSubDirectory(node) ||
HasFiles(node))
EnumerateDirectory(node);
}
foreach (FileInfo file in diRootInfo.GetFiles())
{
TreeNode node = new TreeNode();
node.Text = file.Name;
node.ImageIndex = IconHelper.GetImageIndex(file.Extension);
node.SelectedImageIndex = node.ImageIndex;
parentNode.Nodes.Add(node);
}
}
catch (Exception ex)
{
}
}
This just gets the root directory and checks if it has sub directories and sub files with helper functions, and creates nodes and places them as the correct children and sets the correct image icons for regular and selected. There is a little more to it, but this is the core of the code for creating the initial file tree.
You might have noticed that a node's image index for both normal and selected are being set with the icon helper class:
public static int GetImageIndex(string sType)
{
switch (sType)
{
case ".fx":
return 1;
}
}
This is just a static class with static methods that takes in a file type for this version of the return function, and returns that kind of file's image index. In conjunction with an image list and your own file icons, you can assign any icon to any file type or folder that you desire.
Drag and drop
One of the toughest and most integral components of the file manager is the drag and drop functionality. The drag and drop operation starts with the item drag event:
private void ManagerItemDrag(object sender, ItemDragEventArgs e)
{
TreeNode ndNode = (TreeNode)e.Item;
if (ndNode.ImageIndex != 0)
{
if (CurrentEffect == DragDropEffects.Move)
CurrentState = States.Move;
else
CurrentState = States.Copy;
}
this.DoDragDrop(e.Item, CurrentEffect);
if(ndOld != null)
{
ndOld.BackColor = Color.White;
ndOld.ForeColor = Color.Black;
}
}
The item drag gets the process started. ItemDragEventArgs
holds the data being dragged. Inside the file manager, e
is a TreeNode
, and outside the file manager, this is usually DataFormats.FileDrop
, which contains a string array of the locations of the files that are being dragged. DoDragDrop
sends the data for the drop on through for the rest of the process, and e
can be changed to DataFormats.FileDrop
with a string array of file names to allow drag and drop from within the file manager to outside in Windows Explorer or in other applications, but for right now, I just keep drag and drop operating from outside that manager to in and within the manager itself, but the change can be made easily.
The Drag Enter event is raised when a user drags into the file tree, and this can occur from outside the file manager or within it, and both situations are handled:
private void ManagerDragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop, false) == true)
e.Effect = DragDropEffects.Copy;
else
e.Effect = CurrentEffect;
}
This just checks for data being dragged in from outside the file manager or from within the manager, and sets the effect for drop for pass through.
Next, we have a semi optional event which is the Drag Over event. This occurs when the user is dragging items over the file manager and still has the mouse pressed. This is used for two things. First, it highlights the node being pointed at to let the user know where the drop would occur if the mouse was released at that moment. Second, it takes care of scrolling the file tree if it is larger than the view to let the user navigate to files that are currently out of bounds:
private void ManagerDragOver(object sender, DragEventArgs e)
{
Point pt = ((TreeView)sender).PointToClient(new Point(e.X, e.Y));
TreeNode ndNode = ((TreeView)sender).GetNodeAt(pt);
e.Effect = CurrentEffect;
if (ndNode != null)
{
if (ndNode.ImageIndex == 0)
{
ndNode.BackColor = Color.DarkBlue;
ndNode.ForeColor = Color.White;
if ((ndOld != null) && (ndOld != ndNode))
{
ndOld.BackColor = Color.White;
ndOld.ForeColor = Color.Black;
}
ndOld = ndNode;
}
else if(ndOld != null)
{
ndOld.BackColor = Color.White;
ndOld.ForeColor = Color.Black;
}
}
else if(ndOld != null)
{
ndOld.BackColor = Color.White;
ndOld.ForeColor = Color.Black;
}
ScrollTreeView();
}
protected void ScrollTreeView()
{
const Single scrollRegion = 20;
Point pt = tvFileView.PointToClient(Cursor.Position);
if ((pt.Y + scrollRegion) > tvFileView.Height)
SendMessage(
tvFileView.Handle,
(int)277,
(IntPtr)1,
(IntPtr)0);
else if (pt.Y < (tvFileView.Top + scrollRegion))
SendMessage(
tvFileView.Handle,
(int)277,
(IntPtr)0,
(IntPtr)0);
}
The ScrollTreeView
function uses the cursor position to check the bounds, and sends a message through the Windows procedure to tell the file tree scroll view to scroll and what way to scroll.
The last event in the drag and drop procedure is the Drop Event. There is quite a bit of code in this, but here is the basic outline in the interest of saving space:
private void ManagerDragDrop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop, false) == true)
{
object oFileDrop = null;
IDataObject doData = e.Data;
oFileDrop = doData.GetData(DataFormats.FileDrop, true);
string[] sFiles = (string[])oFileDrop;
}
else
{
bool movingFile = (CurrentEffect == DragDropEffects.Move);
TreeNode NewNode;
if (e.Data.GetDataPresent("System.Windows.Forms.TreeNode", false))
{
}
}
}
As you can see, we just check for either file drop data from outside the file manager, or for a tree node for drag from within the file manager. All it requires to get access to all these events is just going into the file trees events and overriding the needed events with your custom functions.
Context menus
What would a file manager be without the good old right click context menu? It is really easy to create. Just create a ContextMenu
in the Form Designer and add your desired buttons, then just hook into their click events. The first function of the context menu lets the user make new folders from within the file manager:
private void CreateFolderClickHandler(object sender, EventArgs e)
{
if (Directory.Exists(tvFileView.SelectedNode.FullPath + "\\" + "NewFolder"))
{
bool bFound = false;
int nCounter = 1;
while (!bFound)
{
if (!Directory.Exists(
tvFileView.SelectedNode.FullPath + "\\" + "NewFolder" +
"(" + nCounter + ")"))
{
Directory.CreateDirectory(
tvFileView.SelectedNode.FullPath + "\\" +
"NewFolder" + "(" + nCounter + ")");
TreeNode aNode = new TreeNode(("NewFolder" +
"(" + nCounter + ")"));
aNode.ImageIndex = 0;
aNode.SelectedImageIndex = 0;
tvFileView.SelectedNode.Nodes.Add(aNode);
bFound = true;
}
else
nCounter++;
}
}
else
{
Directory.CreateDirectory(tvFileView.SelectedNode.FullPath +
"\\" + "NewFolder");
TreeNode aNode = new TreeNode("NewFolder");
aNode.ImageIndex = 0;
aNode.SelectedImageIndex = 0;
tvFileView.SelectedNode.Nodes.Add(aNode);
}
}
A simple file insertion command is linked to the ContextMenu
. We just check if there is already a folder named "New Folder" and if not, we just create one, or we loop to find the right number to tack on the end.
Next, we have the Cut context menu function:
private void CutClickHandler(object sender, EventArgs e)
{
if (ndCutCopy != null &&
ndCutCopy.ImageIndex >= 18)
{
ndCutCopy.ImageIndex = IconHelper.GetIndexFromCut(ndCutCopy.ImageIndex);
ndCutCopy.SelectedImageIndex =
IconHelper.GetIndexFromCut(
ndCutCopy.SelectedImageIndex);
}
ndCutCopy = tvFileView.SelectedNode;
tvFileView.SelectedNode.ImageIndex = IconHelper.GetCutIndex(
tvFileView.SelectedNode.ImageIndex);
tvFileView.SelectedNode.SelectedImageIndex = IconHelper.GetCutIndex(
tvFileView.SelectedNode.SelectedImageIndex);
CutCopyEffect = DragDropEffects.Move;
SetClipboardData();
bClipPaste = false;
nOperation = 0;
sCutCopy = null;
}
We just get the node being cut and assign the effect for paste and a few other things such as setting data to the Clipboard, which will be covered in a few minutes.
And now, we have the Copy option which is almost identical:
private void CopyClickHandler(object sender, EventArgs e)
{
if (ndCutCopy != null &&
ndCutCopy.ImageIndex >= 18)
{
ndCutCopy.ImageIndex =
IconHelper.GetIndexFromCut(ndCutCopy.ImageIndex);
ndCutCopy.SelectedImageIndex =
IconHelper.GetIndexFromCut(
ndCutCopy.SelectedImageIndex);
}
ndCutCopy = tvFileView.SelectedNode;
CutCopyEffect = DragDropEffects.Copy;
SetClipboardData();
bClipPaste = false;
nOperation = 0;
sCutCopy = null;
}
And here is the SetClipboardData()
function which sets the current file to the clipboard in the DataFormat.FileDrop
format with the file path. Don't worry about how it works right now, we will set that in a minute. Just know that we set a DataObject
with the format of the data and the data we want to set to the Clipboard:
private void SetClipboardData()
{
DataObject doData = new DataObject();
string[] sFile = new string[1];
sFile[0] = tvFileView.SelectedNode.FullPath;
doData.SetData(DataFormats.FileDrop, true, sFile);
Clipboard.SetDataObject(doData, true);
}
Paste is up next. This handles the logic of figuring out if we are pasting from the Clipboard or from a tree node from within the file manager:
private void PasteClickHandler(object sender, EventArgs e)
{
if (bClipPaste)
{
PasteFromClipboard();
}
else
{
if (CutCopyEffect == DragDropEffects.Move)
{
ndCutCopy.ImageIndex = IconHelper.GetIndexFromCut(
ndCutCopy.ImageIndex);
ndCutCopy.SelectedImageIndex =
IconHelper.GetIndexFromCut(
ndCutCopy.SelectedImageIndex);
MoveFileFolder(ndCutCopy, tvFileView.SelectedNode);
ndCutCopy = null;
CutCopyEffect = DragDropEffects.None;
bClipPaste = false;
nOperation = 0;
sCutCopy = null;
}
else if (CutCopyEffect == DragDropEffects.Copy)
{
if (ndCutCopy.ImageIndex == 0 ||
ndCutCopy.ImageIndex == 18)
CopyMoveFolder(ndCutCopy, tvFileView.SelectedNode, true);
else
CopyFile(ndCutCopy, tvFileView.SelectedNode);
ndCutCopy = null;
CutCopyEffect = DragDropEffects.None;
bClipPaste = false;
nOperation = 0;
sCutCopy = null;
}
}
}
The Paste function just checks if we are pasting from the Clipboard (code omitted due to its length; full code is in the project), or from a tree node from within the file manager, and decides if it is a cut or copy and handles those conditions.
And of course, there is the basic Delete function which removes files and folders:
private void DeleteClickHandler(object sender, EventArgs e)
{
if (ndCutCopy != null &&
ndCutCopy.ImageIndex >= 18)
{
ndCutCopy.ImageIndex = IconHelper.GetIndexFromCut(ndCutCopy.ImageIndex);
ndCutCopy.SelectedImageIndex =
IconHelper.GetIndexFromCut(
ndCutCopy.SelectedImageIndex);
}
DeleteNodes(tvFileView.SelectedNode);
}
There is also a Rename function, but the code for this gets lengthy and handles some special conditions, so just check out the source for a full description. You have to check for a lot of special cases like removal of the file extension, duplicate names, and a few other cases, but just check the source and it will explain all.
Clipboard
The infamous Windows Clipboard! Not the most user friendly set up, but we work with what we are given :-). So let's start by getting access to the Clipboard. We need to get access to some User32.dll functions:
[DllImport("user32.dll")]
private static extern int SendMessage(
IntPtr hWnd,
int wMsg,
IntPtr wParam,
IntPtr lParam);
[DllImport("User32.dll")]
protected static extern int SetClipboardViewer(
int hWndNewViewer);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool ChangeClipboardChain(
IntPtr hWndRemove,
IntPtr hWndNewNext);
First, we need the SendMessage()
function to get access to the callback loop to get messages from Windows to know when the Clipboard does something. Next, we need SetClipboardViewer()
to register our file manager as a clipboard viewer to get Clipboard events. Last, we need ChangeClipboardChain()
to remove our app from the chain of viewers when we are done and to know when the chain has changed.
We also need some defines to know what messages we are looking for from the message loop, and a handle pointer to set to the viewer chain to allow us to become a Clipboard viewer:
const int WM_DRAWCLIPBOARD = 0x308;
const int WM_CHANGECBCHAIN = 0x030D;
IntPtr pClipboardViewer;
WM_DRAWCLIPBOARD
is the message that signals a cut or copy has taken place to the Clipboard, and WM_CHANGECBCHAIN
signals that an app has been removed from the viewer chain. We also need to register as a Clipboard viewer, so in the constructor, we add:
pClipboardViewer = (IntPtr)SetClipboardViewer((int)this.Handle);
And we need to unregister our file manager as a Clipboard viewer, so we add the following to Dispose()
:
ChangeClipboardChain(this.Handle, pClipboardViewer);
Now we need a function to handle the Windows message handling procedure:
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_DRAWCLIPBOARD:
SetClipboard();
SendMessage(pClipboardViewer, m.Msg, m.WParam, m.LParam);
break;
case WM_CHANGECBCHAIN:
if (m.WParam == pClipboardViewer)
pClipboardViewer = m.LParam;
else
SendMessage(pClipboardViewer, m.Msg, m.WParam, m.LParam);
break;
default:
base.WndProc(ref m);
break;
}
}
OK! Now that we are registered as viewers for the Clipboard and we can get messages from it, we will look at the SetClipboard()
function which handles getting the file data from the Clipboard when a user performs a Cut or Copy outside of the file manager:
private void SetClipboard()
{
IDataObject doData = Clipboard.GetDataObject();
if (doData.GetDataPresent(DataFormats.FileDrop, true))
{
object oClipboard = null;
MemoryStream msStream =
(MemoryStream)doData.GetData("Preferred DropEffect", true);
int nFlag = 0;
if (msStream != null)
nFlag = msStream.ReadByte();
bool bCut = (nFlag == 2);
bool bCopy = (nFlag == 5);
if (doData != null)
oClipboard = doData.GetData(DataFormats.FileDrop, true);
if (bCut && oClipboard != null)
{
bClipPaste = true;
nOperation = 1;
sCutCopy = (string[])oClipboard;
}
else if (bCopy && oClipboard != null)
{
bClipPaste = true;
nOperation = 2;
sCutCopy = (string[])oClipboard;
}
else
{
bClipPaste = false;
nOperation = 0;
sCutCopy = null;
}
}
else
{
bClipPaste = false;
nOperation = 0;
sCutCopy = null;
}
}
We get our data out of the Clipboard and check to make sure it is in FileDrop
format. We check the first bte to see if it is a cut or copy operation. We then get the list of files out of our Clipboard data. Then we go back to our paste click handler discussed previously and let it do its thing.
Copy and move operations
This is where stuff gets complicated. There are variations to handle file and folder cut (which is a move operation of a kind) and copy functions. They can be used with tree nodes or file paths provided as strings. They also check for duplicate file names and fill out the file tree accordingly, as well as a host of other functions. You will have to check the code to see all of these functions as it would be more code than I can handle to format in the article ;-). All of the operations are well commented, and you should be able to tell what does what and why.
Points of interest
Why oh why does Windows not register the paste event in the Clipboard viewer? Those who have needed to get this event know what I'm talking about ;-) This took quite a bit of work, and I gained a lot of knowledge doing it, so I hope this helps someone else going down the same path. Any bug reports or fixes welcome!
Revisions
Version 1.1
Worked out some of the file access rights problems. When a directory was created on some systems, it restricted and prevented some file move and creation functions. Made alterations to stop this.