Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

File Manager With Clipboard Interaction and Drag and Drop Support

4.00/5 (6 votes)
14 Nov 2010CPOL10 min read 57.4K   1.2K  
A file manager app with Clipboard interaction and drag and drop support.

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

FMStart.png

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:

C#
//Enumerate an individual directories contents.
public void EnumerateDirectory(TreeNode parentNode)
{
    //Attempts to enumerate the directory specified.
    try
    {
        //Holds directory information.
        DirectoryInfo diRootInfo;
        diRootInfo = new DirectoryInfo(parentNode.FullPath + "\\");

        //Loops through directories to enumerate.
        foreach (DirectoryInfo dir in diRootInfo.GetDirectories())
        {
            //Holds new node to add.
            TreeNode node = new TreeNode();
            node.Text = dir.Name;
            node.ImageIndex = 0;
            node.SelectedImageIndex = 0;
            parentNode.Nodes.Add(node);

            //Checks if directory has sub directories or files to add.
            if (HasSubDirectory(node) ||
                HasFiles(node))
                EnumerateDirectory(node);
        }
        //Loop to Enumerates files.
        foreach (FileInfo file in diRootInfo.GetFiles())
        {
            //Creates node and assigns icon.
            TreeNode node = new TreeNode();
            node.Text = file.Name;
            node.ImageIndex = IconHelper.GetImageIndex(file.Extension);
            node.SelectedImageIndex = node.ImageIndex;

            //Adds node.
            parentNode.Nodes.Add(node);
        }//End file enumerate loop.
    }//End try.
    catch (Exception ex)
    {
    }
}//End enumerate directory.

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:

C#
//Returns file types image index.
public static int GetImageIndex(string sType)
{
    //Image return switch.
    switch (sType)
    {
        case ".fx":
            return 1;
        /*
        Check for you own custom file types here.
        */
    }
}

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

FMDDOne.png

FMDDTwo.png

FMDDThree.png

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:

C#
//Handles the initial item drag.
private void ManagerItemDrag(object sender, ItemDragEventArgs e)
{
    //Hold selected node from mouse pointer.
    TreeNode ndNode = (TreeNode)e.Item;

    //Checks if node pointed to is a file.
    if (ndNode.ImageIndex != 0)
    {
        if (CurrentEffect == DragDropEffects.Move)
            CurrentState = States.Move;
        else
            CurrentState = States.Copy;
    }//End file check.

    //Sets the effect for pass through.
    this.DoDragDrop(e.Item, CurrentEffect);

    //Clears old node highlight.
    if(ndOld != null)
    {
        //Clears old node.
        ndOld.BackColor = Color.White;
        ndOld.ForeColor = Color.Black;
    }
}//End manager item drag.

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:

C#
//Handles drag enter event.
private void ManagerDragEnter(object sender, DragEventArgs e)
{
    //Checks for incoming files from outside the manager.
    if (e.Data.GetDataPresent(DataFormats.FileDrop, false) == true)
        e.Effect = DragDropEffects.Copy;
    else//Sets the effect for pass through.
        e.Effect = CurrentEffect;
}//End drag enter.

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:

C#
//Handles drag over events.
private void ManagerDragOver(object sender, DragEventArgs e)
{
    //Holds point position of mouse cursor.
    Point pt = ((TreeView)sender).PointToClient(new Point(e.X, e.Y));
    //Holds the destination node.
    TreeNode ndNode = ((TreeView)sender).GetNodeAt(pt);

    //Sets effect for pass through.
    e.Effect = CurrentEffect;
            
    //Checks if mouse points to a node.
    if (ndNode != null)
    {
        //Checks if a file node is selected.
        if (ndNode.ImageIndex == 0)
        {
            //Sets scroll over drag node color.
            ndNode.BackColor = Color.DarkBlue;
            ndNode.ForeColor = Color.White;

            //Checks if a new node was selected.
            if ((ndOld != null) && (ndOld != ndNode))
            {
                //Clears old node.
                ndOld.BackColor = Color.White;
                ndOld.ForeColor = Color.Black;
            }//End select check.

            //Sets new old node.
            ndOld = ndNode;
        }//End file node check.
        else if(ndOld != null)
        {
            //Clears old node.
            ndOld.BackColor = Color.White;
            ndOld.ForeColor = Color.Black;
        }
    }//End node pointed to check.
    else if(ndOld != null)
    {
        //Clears old node.
        ndOld.BackColor = Color.White;
        ndOld.ForeColor = Color.Black;
    }

    //Checks for tree scroll.
    ScrollTreeView();
}//End manager drag over.


//Checks for tree scroll.
protected void ScrollTreeView()
{
    const Single scrollRegion = 20;
    // See where the cursor is
    Point pt = tvFileView.PointToClient(Cursor.Position);

    //Checks for scroll down
    if ((pt.Y + scrollRegion) > tvFileView.Height)
        SendMessage(
            tvFileView.Handle, 
            (int)277,
            (IntPtr)1, 
            (IntPtr)0);
    else if (pt.Y < (tvFileView.Top + scrollRegion))//Checks scroll up.
        SendMessage(
            tvFileView.Handle,
            (int)277,
            (IntPtr)0, 
            (IntPtr)0);
}//End scroll tree view.

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:

C#
//Handles the drop event.
private void ManagerDragDrop(object sender, DragEventArgs e)
{
    //Checks for file drop from outside.
    if (e.Data.GetDataPresent(DataFormats.FileDrop, false) == true)
    {
        //Holds file drop data.
        object oFileDrop = null;
        //Holds clipboard data.
        IDataObject doData = e.Data;
        //Array of filenames present inside clipboard
        oFileDrop = doData.GetData(DataFormats.FileDrop, true);
        //Holds files to copy.
        string[] sFiles = (string[])oFileDrop;
 
        /*
        Do what you wish with the files here 
        by using their paths to operate on them.
        */
    }
    else//Checks for ManagerDragDrop drop.
    {
        //Called last when mouse released during a drop
        bool movingFile = (CurrentEffect == DragDropEffects.Move);
        //Holds node to be copied or moved.
        TreeNode NewNode;

        //Checks for data in the node.
        if (e.Data.GetDataPresent("System.Windows.Forms.TreeNode", false))
        {
            /*
            Do what you wish with the tree node being draged here.
            */
        }
    }
}

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:

C#
//Handles creation menu item click
private void CreateFolderClickHandler(object sender, EventArgs e)
{
    //Checks if new folder is used.
    if (Directory.Exists(tvFileView.SelectedNode.FullPath + "\\" + "NewFolder"))
    {
        //Flag to tell if file name found.
        bool bFound = false;
        //Counter to add to file name.
        int nCounter = 1;

        //Loops to find usable file name.
        while (!bFound)
        {
            //Checks for unused file name.
            if (!Directory.Exists(
                    tvFileView.SelectedNode.FullPath + "\\" + "NewFolder" + 
                    "(" + nCounter + ")"))
            {
                //Creates directory.
                Directory.CreateDirectory(
                    tvFileView.SelectedNode.FullPath + "\\" + 
                    "NewFolder" + "(" + nCounter + ")");
                //Holds new node.
                TreeNode aNode = new TreeNode(("NewFolder" + 
                    "(" + nCounter + ")"));
                //Sets node image.
                aNode.ImageIndex = 0;
                aNode.SelectedImageIndex = 0;
                //Adds node to file tree.
                tvFileView.SelectedNode.Nodes.Add(aNode);
                //Sets found flag.
                bFound = true;
            }
            else//Increments counter.
                nCounter++;
        }//End file name found check.
    }//End used name check.
    else//Creates new folder.
    {
        //Creates directory.
        Directory.CreateDirectory(tvFileView.SelectedNode.FullPath +
            "\\" + "NewFolder");
        //Holds new node.
        TreeNode aNode = new TreeNode("NewFolder");
        //Sets node image.
        aNode.ImageIndex = 0;
        aNode.SelectedImageIndex = 0;
        //Adds node to file tree.
        tvFileView.SelectedNode.Nodes.Add(aNode);
    }//End new folder creation check.
}//End create click handler.

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:

C#
//Handles cut context menu item click.
private void CutClickHandler(object sender, EventArgs e)
{
    //Sets old node to regular.
    if (ndCutCopy != null &&
        ndCutCopy.ImageIndex >= 18)
    {
        ndCutCopy.ImageIndex = IconHelper.GetIndexFromCut(ndCutCopy.ImageIndex);
        ndCutCopy.SelectedImageIndex = 
            IconHelper.GetIndexFromCut(
                ndCutCopy.SelectedImageIndex);
    }
    //Sets cut node.
    ndCutCopy = tvFileView.SelectedNode;
    tvFileView.SelectedNode.ImageIndex = IconHelper.GetCutIndex(
        tvFileView.SelectedNode.ImageIndex);
    tvFileView.SelectedNode.SelectedImageIndex = IconHelper.GetCutIndex(
        tvFileView.SelectedNode.SelectedImageIndex);
    //Sets cut/copy effect.
    CutCopyEffect = DragDropEffects.Move;

    //Sets clipboard data.
    SetClipboardData();

    //Resets cut / copy variables.
    bClipPaste = false;
    nOperation = 0;
    sCutCopy = null;
}//End cut click handler.

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:

C#
//Handles copy context menu item click.
private void CopyClickHandler(object sender, EventArgs e)
{
    //Sets old node to regular.
    if (ndCutCopy != null &&
        ndCutCopy.ImageIndex >= 18)
    {
        ndCutCopy.ImageIndex = 
              IconHelper.GetIndexFromCut(ndCutCopy.ImageIndex);
        ndCutCopy.SelectedImageIndex =
            IconHelper.GetIndexFromCut(
                ndCutCopy.SelectedImageIndex);
    }
    //Sets copy node.
    ndCutCopy = tvFileView.SelectedNode;
    //Sets cut/copy effect.
    CutCopyEffect = DragDropEffects.Copy;

    //Sets clipboard data variables.
    SetClipboardData();

    //Resets cut / copy variables.
    bClipPaste = false;
    nOperation = 0;
    sCutCopy = null;
}//End copy click handler.

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:

C#
//Sets file to 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);
}//End set clipboard data.

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:

C#
//Handles paste context menu item click.
private void PasteClickHandler(object sender, EventArgs e)
{
    //Checks for clipboard paste.
    if (bClipPaste)
    {
        PasteFromClipboard();
    }//End clipboard past check.
    else//Checks for node copy paste.
    {
        //Checks for cut.
        if (CutCopyEffect == DragDropEffects.Move)
        {
            //Sets to regular image.
            ndCutCopy.ImageIndex = IconHelper.GetIndexFromCut(
                ndCutCopy.ImageIndex);
            ndCutCopy.SelectedImageIndex = 
                IconHelper.GetIndexFromCut(
                    ndCutCopy.SelectedImageIndex);
            //Moves files and folders.
            MoveFileFolder(ndCutCopy, tvFileView.SelectedNode);
            //
            ndCutCopy = null;
            //Sets cut/copy effect.
            CutCopyEffect = DragDropEffects.None;

            //Resets cut / copy variables.
            bClipPaste = false;
            nOperation = 0;
            sCutCopy = null;
        }//End cut check.
        else if (CutCopyEffect == DragDropEffects.Copy)//Checks for copy.
        {
            //Checks for folder.
            if (ndCutCopy.ImageIndex == 0 ||
                ndCutCopy.ImageIndex == 18)
                CopyMoveFolder(ndCutCopy, tvFileView.SelectedNode, true);
            else//Checks for file.
                CopyFile(ndCutCopy, tvFileView.SelectedNode);
            //Resets the cut/copy node.
            ndCutCopy = null;
            //Sets cut/copy effect.
            CutCopyEffect = DragDropEffects.None;

            //Resets cut / copy variables.
            bClipPaste = false;
            nOperation = 0;
            sCutCopy = null;
        }//End copy check.
    }//End node copy paste.
}//End paste click handler.

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:

C#
//Handles delete context menu click.
private void DeleteClickHandler(object sender, EventArgs e)
{
    //Sets old node to regular.
    if (ndCutCopy != null &&
        ndCutCopy.ImageIndex >= 18)
    {
        ndCutCopy.ImageIndex = IconHelper.GetIndexFromCut(ndCutCopy.ImageIndex);
        ndCutCopy.SelectedImageIndex = 
            IconHelper.GetIndexFromCut(
                ndCutCopy.SelectedImageIndex);
    }
    //Deletes node selected.
    DeleteNodes(tvFileView.SelectedNode);
}//End delete click handler.

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

FMCBCutOne.png

FMCBCutTwo.png

FMCBCutThree.png

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:

C#
//Imports windows callback loop send message function.
[DllImport("user32.dll")]
private static extern int SendMessage(
    IntPtr hWnd,
    int wMsg, 
    IntPtr wParam, 
    IntPtr lParam);
//Used to set control as a clipboard viewer.
[DllImport("User32.dll")]
protected static extern int SetClipboardViewer(
    int hWndNewViewer);
//Used to unregister as a clipboard viewer.
[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:

C#
//Identifier for clipboard change.
const int WM_DRAWCLIPBOARD = 0x308;
//Identifier for clipboard chain change.
const int WM_CHANGECBCHAIN = 0x030D;
//Holds poiter to the next clipboard viewer in the chain.
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:

C#
//Sets the next clipboard viwer in the chain.
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():

C#
//Notifies clipboard chain of control destruction.
ChangeClipboardChain(this.Handle, pClipboardViewer);

Now we need a function to handle the Windows message handling procedure:

C#
//Handles windows procedure messages.
protected override void WndProc(ref Message m)
{
    switch (m.Msg)
    {
        case WM_DRAWCLIPBOARD:
            //Sets clipboard contents.
            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;
    } 
}//End wndproc.

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:

C#
//Handles contents of the clipboard.
private void SetClipboard()
{
    //Holds clipboard data.
    IDataObject doData = Clipboard.GetDataObject();

    //Checks for file drop data.
    if (doData.GetDataPresent(DataFormats.FileDrop, true))
    {
        //Holds file drop data.
        object oClipboard = null;
        MemoryStream msStream = 
           (MemoryStream)doData.GetData("Preferred DropEffect", true);
        //Holds cut or copy bype.
        int nFlag = 0;
        //Trys to get the first byte.
        if (msStream != null)
            nFlag = msStream.ReadByte();

        //Flags to tell if cut or copy clipboard.
        bool bCut = (nFlag == 2);
        bool bCopy = (nFlag == 5);

        //Get array of filenames present inside clipboard
        if (doData != null)
            oClipboard = doData.GetData(DataFormats.FileDrop, true);

        //Sets paste from clipboard flag.
        if (bCut && oClipboard != null)
        {
            //Sets for copy paste.
            bClipPaste = true;
            nOperation = 1;
            sCutCopy = (string[])oClipboard;
        }
        else if (bCopy && oClipboard != null)
        {
            //Sets for cut paste.
            bClipPaste = true;
            nOperation = 2;
            sCutCopy = (string[])oClipboard;
        }
        else
        {
            //Resets cut / copy variables.
            bClipPaste = false;
            nOperation = 0;
            sCutCopy = null;
        }
    }//End file drop data check.
    else//Checks for non file drop data.
    {
        //Resets cut / copy variables.
        bClipPaste = false;
        nOperation = 0;
        sCutCopy = null;

        /*
        Note: If a user has cut a file in the windows explorer
        and does a paste into the explorer it registers here.
        This is by no means a true paste event
        but could be useful, but unreliable.
        */
    }//End non file drop data check.
}//End paste from clipboard.

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)