Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

C# File Browser

0.00/5 (No votes)
21 Aug 2006 1  
A file browser written in C#, very much like Windows Explorer.

Contents

Introduction

This project introduces a Windows Explorer clone in an early state. It contains browsing through all files and folders on your computer, including virtual folders. It uses the same ContextMenus as Windows Explorer and includes drag and drop support.

I created this project with Visual Studio 2005 (.NET 2.0) and haven't tried it with .NET 1.1. I'm pretty sure it will work with .NET 1.1, but some changes need to be made. For example, I used the ToolBarMenuStrip which isn't available in 1.1. If anyone wants a 1.1 version and isn't able to convert it, I'm willing to convert it myself and provide the code.

Update V1.3

Quite a lot of different new things in this update. The most important new feature is plug-ins. You can now add your own plugins to this application, which will be used to add columns to the details view and to add special views. More on this in the "Plug-ins" section. Another important update is an addition to this article which explains how to use this Control in your own application and how to use it's functions, see the "Using the Control" section for this update.

Furthermore there are some small updates, additions and bug fixes. See the "History" section for these updates.

Update V1.2

A bit of a small update really, but quite a handy one. I added the "New" menu to the standard ContextMenu of the ListView. So now it's possible to add new folders and files from within the program.

Also a few bug fixes and another change in the update thread has been made, which also comes with some speed improvement.

Update V1.1

This is quite a large improvement since the first version. It doesn't include a lot of new features, but has a lot of fixes and speed improvement. The most important fix is the memory leak fix, which was caused by the update thread. When I was solving this problem I also added another update method. This method uses the SHChangeNotifyRegister function to retrieve Shell notify messages. These messages are used to make some more updates, like changing icons, inserting media and renaming. So now when you insert a disc into your disc-drive the icon and text of the drive will change to the ones from the disc.

One new feature which needs some attention is the rename function. You can now rename items by selecting the rename item from their ContextMenu or by pressing F2. Be aware though that this will also change the extension of the file, but it will warn you if you do this. When renaming multiple items it will take the name you entered and add a number to it, different for any item, somewhat like Windows Explorer. I recommend trying the rename function on some test files to see exactly what is does, before using it on other files.

For any other changes made, see the "History" section.

Background

I was looking for something nice to program, when I got the idea of making my own Windows Explorer. I started this project with the idea of making an enhanced version of Windows Explorer with plug-in support. But before being able to enhance the Windows Explorer, you got to have a program which works like Windows Explorer. So I started searching the Internet for solutions.

While searching the Internet I found a lot of programming around Windows Explorer and Shell extensions. But none really had everything I needed and most programs where written in C++ while I really wanted one in C#. Finally I found the article I needed to start my program: An All VB.NET Explorer Tree Control with ImageList Management. Although it was written in VB I could get a really great deal of information from it and the largest part of this project relies on that article. So for more information or for a VB version see that article.

The only problem now was that I had never worked with the Windows Shell before. So first things first, I searched for articles explaining about how the Shell works and what you can do with it. Well, you can do a lot with it, too much to explain here. If you never worked with the Shell before or don't really know how the Shell works, I recommend this article: C# does Shell. This article also provides you with some links to MSDN articles. It takes some time to read them, but it definitely helped me a lot in making this program. You can also find a lot of info about all the Shell methods, structures and enumerations used in this program on MSDN.

After the base of the program was created I started implementing things like the Shell ContextMenu, drag/drop support and a Windows Explorer like ComboBox. I didn't really find a nice article on CodeProject for this, but a whole bunch where available on the Internet. I programmed everything with a wrapper around the Shell functions from the Windows API and was surprised how well it worked.

Using the Control

To use this control in your own program, add a reference to the dll to your project. After that you can add the Browser control to the toolbox and add it to your own project. I've created some properties which allow you to alter the behaviour and look of the control (at design time):

ShowNavigationBar Shows or hides the navigation bar
ShowFolders Shows or hides the folder TreeView
ShowFoldersButton Shows or hides the button for the folder TreeView
StartUpDirectory Enumeration indicating which directory will be opened at startup
StartUpDirectoryOther String indicating which directory will be opened at startup (StartUpDirectory must be "Other")
ShellBrowser Sets the ShellBrowser used for retrieving ShellItems, if set to null the Browser will create it's own
PluginWrapper Sets the PluginWrapper used for retrieving plug-ins, if set to null the Browser will create it's own
SplitterDistance Sets the distance of the Splitter between the TreeView and the ListView>/TD>

StartUpDirectory is an enumeration of special folders which are used to determine the startup location of the Browser. If you want to provide your own location, you must set this value to "Other" and provide your own location in the StartUpDirectoryOther property.

The ShellBrowser and PluginWrapper properties are used when you want to add more than one Browser control to your program. You can link those Browser controls by setting the ShellBrowser and PluginWrapper to the same object. This will make the program run a lot faster and more efficient than using a different ShellBrowser and PluginWrapper for the controls. In the demo project you'll see an example of how to add two Browser controls to a project.

There are also a few properties which you can only use at run-time:

ListViewMode Sets the initial view of the ListView (can't be "Small Icons")
SelectedItem Sets the current directory (ShellItem)
SelectedNode Sets the current directory (TreeNode)
ShowFoldersButton Shows or hides the button for the folder TreeView

Lastly, there are methods to programmatically do some actions for the Browser:

SelectPath Sets the current directory
BrowserBack Same as clicking the Back button of the Browser
BrowserForward Same as clicking the Forward button of the Browser
BrowserUp Same as clicking the Up button of the Browser
CreateNewFolder Creates a new directory in the current directory, if possible

SelectPath takes 3 different Objects to set the current directory. Either the ShellItem of the folder to select, a string of the path to the directory (this can also be like "My Documents\My Music") or a value of the SpecialFolders enumeration.

Class Overview

The main classes

These are the classes which provide the actual control.

Browser The actual FileBrowser control
BrowserTreeView, BrowserListView and BrowserComboBox The controls used in the Browser
BrowserTreeSorter and BrowserListSorter The classes for sorting the TreeNodes and ListViewItems
BrowserComboItem Provides the items for the BrowserComboBox

These classes are quite simple and don't need a lot of explanation. To use my control in your project, you actually only need to use the Browser class. Just add this control to a form and all should be working. For more info, take a look at the comment in my code. Unfortunately at the moment my code doesn't have many comments, but I will try to add more shortly.

The Shell classes

These classes provide easy access to Shell functions.

ShellAPI Includes Windows API imports, constants, structures, and enumerations
ShellBrowser Used to retrieve the ShellItems which represent the file system
ShellItem Represents a file system object, folder or file (this can be a virtual folder)
ShellImageList Retrieves the Shell ImageList and makes them available for the Browser
PIDL Build around a pointer to a PIDL-structure, which is used to identify a file system object

ShellAPI and ShellImageList are very much like the classes in Jim Parsells' project I mentioned earlier. They are similar to ShellDll and SystemImageListManager respectively. For more info about these classes first try his article. ShellItem comes from his CShItem class, but I've completely rewritten it, to match my needs. I'm not going to go through the detail of this class, but if many people really need more info about it, I might write an article about it.

The wrapper classes

These classes provide a wrapper around the drag/drop operations and the ContextMenus for the control.

BrowserTVContextMenuWrapper and BrowserLVContextMenuWrapper

Provide ContextMenus to the TreeView and ListView

ContextMenuHelper Takes care of executing Shell ContextMenu commands
BrowserTVDropWrapper and BrowserLVDropWrapper Provide drop operations to the TreeView and ListView
BrowserTVDragWrapper and BrowserLVDragWrapper Provide drag operations to the TreeView and ListView

These classes are the most important and they are the ones I will explain in the rest of this article.

The Shell ContextMenu

Retrieving it

The first thing you'll notice when trying to find a nice article about getting the Shell ContextMenu in your program, is that almost all articles are about making extensions to the menu and not about retrieving it for your own program. Luckily I found one blog that did explain this very thoroughly: How to host an IContextMenu. It was all in C++, so I had to translate it to C#. As this is an article existing of 11 parts, I'll try to explain everything here from a C# point of view. I'm going to assume you are familiar with the Shell namespace and pidls as it would take a lot of time to explain this here and this article is meant to cover the ContextMenu. So if you are not familiar with these terms, look for an article on those things first. The one I mentioned in the start of this article was all I needed (C# does Shell).

Before I explain the procedure for showing the ContextMenu, I'll give a short description for the interfaces we are going to use:

IShellFolder Is used to manage folders, it is exposed by all Shell namespace folder objects
IContextMenu Is called by the Shell to either create or merge a shortcut menu associated with a Shell object
IContextMenu2 Is used to either create or merge a shortcut menu associated with a certain object when the menu involves owner-drawn menu items
IContextMenu3 Is used to create or merge a shortcut menu associated with a certain object when the menu implementation needs to process the WM_MENUCHAR message

So know let's get started with the ContextMenu stuff. To retrieve the menu you want, you'll need a few things:

  • The IShellFolder interface from the parent directory
  • The pidls (relative to the parent) from the items you want to get the ContextMenu for
  • The IContextMenu from the same items

Not much has to be done to obtain the IShellFolder interface. The ShellItem class provides the IShellFolder for each directory, so you just have to get the ShellItem class for the parent directory and then you'll have the IShellFolder interface. The pidls can also be retrieved from the ShellItem class. In my control each TreeNode and ListViewItem has their own ShellItem in their Tag property, so it is also quite easy to get the pidls you need. After this has been done you have everything you to get the IContextMenu interface. The IShellFolder interface has a method which will provide a lot of different interfaces for its children, these interfaces include the IContextMenu. We need to make a call to the GetUIObjectOf method from the IShellFolder, like in the following example:

public static bool GetIContextMenu(
          IShellFolder parent,
          IntPtr[] pidls,
          out IntPtr icontextMenuPtr,
          out IContextMenu iContextMenu)
{
    if (parent.GetUIObjectOf(
                IntPtr.Zero,
                (uint)pidls.Length,
                pidls,
                ref ShellAPI.IID_IContextMenu,
                IntPtr.Zero,
                out icontextMenuPtr) == ShellAPI.S_OK)
    {
        iContextMenu =
            (IContextMenu)Marshal.GetTypedObjectForIUnknown(
                icontextMenuPtr, typeof(IContextMenu));

        return true;
    }
    else
    {
        icontextMenuPtr = IntPtr.Zero;
        iContextMenu = null;

        return false;
    }
}

As you can see, you need an array of IntPtr. This array includes the pidls of the items for which to retrieve the IContextMenu. This can be any number, in our program this number depends on how many items are selected. With GetUIObjectOf you'll get a pointer to the IContextMenu and to obtain the real interface you need to use the Marshal class.

Now we need a ContextMenu and because we are calling only Windows API methods, all we need is a Handle to a ContextMenu. To make a new ContextMenu the Windows API way, we just need to call ShellAPI.CreatePopupMenu(), which will return a pointer to the new ContextMenu. You can now add all the menu items from the Shell ContextMenu by calling the QueryContextMenu method from the IContextMenu interface.

contextMenu = ShellAPI.CreatePopupMenu();
  
iContextMenu.QueryContextMenu(
  contextMenu,
  0,
  ShellAPI.CMD_FIRST,
  ShellAPI.CMD_LAST,
  ShellAPI.CMF.EXPLORE |
  ShellAPI.CMF.CANRENAME |
  ((Control.ModifierKeys & Keys.Shift) != 0 ? 
    ShellAPI.CMF.EXTENDEDVERBS : 0));

Invoking the selected command

Now the contextMenu pointer points to the ContextMenu we need. After this call you can change the menu in any way you want. To change the menu you can use the API functions AppendMenu and InsertMenu from the ShellAPI class. After that it's time to show our menu to the user. We do this by calling ShellAPI.TrackPopupMenuEx. This method will wait for the user to select an item and will return the id of the selected item. This id is not just the index of the item in the list, but it's a special id. To execute the command that goes with the selected item we need a CMINVOKECOMMANDINFOEX structure. We can use this with the InvokeCommand method from the IContextMenu to execute the selected command. For more info about this structure see MSDN.

ShellAPI.CMINVOKECOMMANDINFOEX invoke = 
        new ShellAPI.CMINVOKECOMMANDINFOEX();
invoke.cbSize = ShellAPI.cbInvokeCommand;
invoke.lpVerb = (IntPtr)cmd;
invoke.lpDirectory = parentDir;
invoke.lpVerbW = (IntPtr)cmd;
invoke.lpDirectoryW = parentDir;
invoke.fMask = ShellAPI.CMIC.UNICODE | ShellAPI.CMIC.PTINVOKE |
    ((Control.ModifierKeys & Keys.Control) != 0 ? ShellAPI.CMIC.CONTROL_DOWN : 0) |
    ((Control.ModifierKeys & Keys.Shift) != 0 ? ShellAPI.CMIC.SHIFT_DOWN : 0);
invoke.ptInvoke = new ShellAPI.POINT(ptInvoke.X, ptInvoke.Y);
invoke.nShow = ShellAPI.SW.SHOWNORMAL;

iContextMenu.InvokeCommand(ref invoke);

In the previous example the cmd variable is the selected index. All we need to do is cast this to a pointer and the Shell functions know what to do with it. As you can see I also included some code for ModfierKeys. As you might know, when you delete a file using Windows Explorer, there are two ways to do it: moving it to the recycle bin, or deleting it permanently. When you just press delete, the selected file will be moved into the recycle bin, but when you hold shift and press delete, the file will be deleted permanently. That is why you have to add the ModifierKeys to the structure.

Another thing to notice is that we add a POINT to the structure. This POINT represents the place on the screen where you pressed the right mouse button. Have you ever noticed that when you clicked Properties on the ContextMenu of Windows Explorer, that the Properties window will be shown on the point where you right clicked your mouse button? Well it does and to have the same effect in your program you will have to set this POINT.

The "Open With" and "Send To" submenus

When all of this worked I was really happy, but soon I found something strange. When you select the "Open With" or the "Send To" submenus, you don't see other menu items in it. As we also want this menus to work, we need to get some more interfaces. The IContextMenu has two child classes which are needed to get the menu's to work: IContextMenu2 and IContextMenu3. To get these interfaces we simply use the Marshal class like this:

Marshal.QueryInterface(
    icontextMenuPtr, ref ShellAPI.IContextMenu2_IID, out context2Ptr);
    
Marshal.QueryInterface(
    icontextMenuPtr, ref ShellAPI.IContextMenu3_IID, out context3Ptr);
    
iContextMenu2 =
    (IContextMenu2)
        Marshal.GetTypedObjectForIUnknown(context2Ptr, typeof(IContextMenu2));

iContextMenu3 =
    (IContextMenu3)
        Marshal.GetTypedObjectForIUnknown(context3Ptr, 
        typeof(IContextMenu3));

These interfaces will draw the menus for us, but they need to know when to do this. For this we need to override the WndProc method and check the messages that are being send to it. When these messages are about creating, measuring or drawing the ContextMenu items we will call the HandleMenuMsg and HandleMenuMsg2 methods from the IContextMenu2 and IContextMenu3 interfaces respectively, these methods will do the rest of the necessary work.

As you can read on MSDN, the IContextMenu2 interface will process the WM_INITMENUPOPUP, WM_MEASUREITEM and WM_DRAWITEM messages and the IContextMenu3 interface will process the WM_MENUCHAR message. So if you encounter one of these messages while showing the ContextMenu call the HandleMenuMsg and HandleMenuMsg2 methods to handle the specific messages.

protected override void WndProc(ref Message m)
{
    if (iContextMenu2 != null &&
        (m.Msg == (int)ShellAPI.WM.INITMENUPOPUP ||
         m.Msg == (int)ShellAPI.WM.MEASUREITEM ||
         m.Msg == (int)ShellAPI.WM.DRAWITEM))
    {
        if (iContextMenu2.HandleMenuMsg(
            (uint)m.Msg, m.WParam, m.LParam) == ShellAPI.S_OK)
            return;
    }
    
    if (iContextMenu3 != null &&
        m.Msg == (int)ShellAPI.WM.MENUCHAR)
    {
        if (iContextMenu3.HandleMenuMsg2(
            (uint)m.Msg, m.WParam, m.LParam, IntPtr.Zero) == ShellAPI.S_OK)
            return;
    }
    
    base.WmdProc(ref Message m);
}

Once you implemented this, you will see that now the submenu's will also work the way they are supposed to.

This is the main idea to get the ContextMenus to work. My program also adds the Collapse and Expand MenuItems on a TreeNode ContextMenu, like Windows Explorer does. It will also raise an event for showing the ContextMenuItems help String when the item is being hovered over. Just check my code if you need to know how to do this.

Drag and drop support

Before I started implementing the drag/drop support to my program I read the following article by Jim Parsells: Adding Drag and Drop to an Explorer Tree Control and another one by Michael Dunn: How to Implement Drag and Drop Between Your Program and Explorer. These let me in the right way and also made it a bit more challenging for me. At the end of Jim Parsells article he mentions some problems when implementing it the way he did. I think I solved these problems, by implementing it without using the .Net drag/drop methods. So forget all the nice implementations of .Net, we are going to use the Windows API to get this to work.

Three new interfaces are needed for drag and drop support:

IDropTarget Contains methods used in any application that can be a target for data during a drag/drop operation
IDropSource Contains the methods for generating visual feedback to the end user and for canceling or completing the drag/drop operation
IDataObject Is used by the Clipboard class and in drag/drop operations to store data from the dragged object

Fortunately we have the Shell namespace

The nice thing of the Shell namespace is that it will do all the dirty work for you, the only problem is to figure out how to let the Shell do his job. Once you are working with the Shell and get more familiar with it however, this will get a lot easier and you will find solutions to your problem quite fast. Once I got the hang of the ContextMenu stuff, it was actually quite an easy job for me to implement drag/drop.

Dropping items onto your control

To register your program for drop operations you need a class which implements the IDropTarget interface. In my program the BrowserTVDropWrapper and the BrowserLVDropWrapper are both IDropTargets. Before we get the necessary events raised in our own classes we need to register them. You can do this by calling the ShellAPI.RegisterDragDrop method, this method takes two arguments. One argument being the handle of the control to register the drag operation for and the other being the IDropTarget to receive messages about the drag. You also need to revoke your registration once you program finishes using the ShellAPI.RevokeDragDrop method. Once you registered your IDropTarget, your class will receive 4 different messages which need some more attention.

The first message being DragEnter, which will be called when someone drags an object and enters you control. You will receive a pointer to the IDataObject being dragged, the current state of the modifier keys and mouse buttons, the location of the mouse pointer and a reference to an instance of the DragDropEffects enumeration. This is quite a lot of info, but we don't really need to use it all. The Shell provides us with an IDropTarget from specific Shell objects which will do all the work for us. The only thing we need to do is check which item is being dragged over, obtain the IDropTarget for that item and pass all the info to that interface. To get the IDropTarget from an item, we have to call the GetUIObjectOf method from the parent IShellFolder interface again (as with the IContextMenu interface). So the basic idea in code form looks like this:

private ShellDll.IDropTarget GetIDropTarget(ShellItem item, 
                             out IntPtr dropTargetPtr)
{
    ShellItem parent = item.ParentItem != null ? item.ParentItem : item;

    if (parent.ShellFolder.GetUIObjectOf(
            IntPtr.Zero,
            1,
            new IntPtr[] { item.PIDLRel.Ptr },
            ref ShellAPI.IID_IDropTarget,
            IntPtr.Zero,
            out dropTargetPtr) == ShellAPI.S_OK)
    {
        ShellDll.IDropTarget target =
            (ShellDll.IDropTarget)Marshal.GetTypedObjectForIUnknown(
                dropTargetPtr, typeof(ShellDll.IDropTarget));

        return target;
    }
    else
    {
        dropTargetPtr = IntPtr.Zero;
        return null;
    }
}

public int DragEnter(
    IntPtr pDataObj, 
    ShellAPI.MK grfKeyState, 
    ShellAPI.POINT pt, 
    ref DragDropEffects pdwEffect)
{
    Point point = br.FolderView.PointToClient(new Point(pt.x, pt.y));
    TreeViewHitTestInfo hitTest = br.FolderView.HitTest(point);

    dropNode = hitTest.Node;

    if (dropNode != null)
    {
        ShellItem item = (ShellItem)dropNode.Tag;
        parentDropItem = item;
    
        dropTarget = GetIDropTarget(item, out dropTargetPtr);
    
        if (dropTarget != null)
        {
            dropTarget.DragEnter(pDataObj, grfKeyState, pt, ref pdwEffect);
        }
    }
    
    return ShellAPI.S_OK;
}

When the DragEnter method has been called, the DragOver method will be called many times while the dragged item is over your control. This way you can give specific information on where the dragged item can be dropped and where it can't be. We can once again let the Shell do all the dirty work, just like with the DragEnter method.

Now there are two methods left, either the drag operation on your control will be canceled, or it will succeed and the item is dropped on your control. For when the operation is cancelled there is the DragLeave method. There is no additional info given, just a notice that the drag has ended on your control. Nothing much has to be done now, except for preparing your class to receive another drag operation.

If the drop succeeds the DragDrop method will be called providing you with pretty much the same info as the DragEnter and DragOver methods. The only difference being that some action has to be taken. Once again this action will be performed by the Windows Shell. When we call the DragDrop method from the IDropTarget which we retrieved earlier, the Shell does all the work for us. All the same notifications and process windows are shown like when you are using Windows Explorer.

Well that's pretty much it for the drop operations. In my classes a bit more has been done to give it all a nice look. This includes selecting the node over which you are dragging an object, and showing the nice ghost image from the content you are dragging which Windows Explorer shows. But these things aren't really necessary to get it all working.

Dragging items from your control

Once the drop part is out of the way, it's time to implement the drag operations. This is the part where it's getting a bit different from the VB explorer. In Jim Parsell's explorer, he uses a special class to create IDataObjects for the items being dragged. He's doing it the .NET way by making use of .NET's IDataObject interface, but actually you do not really need to do anything with the IDataObject interface other than passing it on. That is, if you are doing it the Shell way, which is in my opinion a lot easier than the .Net way.

Because we are going to drag items using API methods, we are going to need an IDropSource interface. This interface will take care of any drawing or canceling while dragging your item. The BrowserTVDragWrapper and BrowserLVDragWrapper classes implement this interface, and they will make sure dragging will be supported.

The first thing we need is to get a notification when an item is being dragged. Both the TreeView and ListView have an event for this (ItemDrag), so we just register to it. Once a drag has been initialized we need to make a call to an API method, to register the wrapper as an IDropSource and to trigger the drag. The method to call is ShellAPI.DoDragDrop, it has two input arguments and one output. The two input arguments are the IDataObject from the item being dragged and an instance of the DragDropEffects enumeration telling the method which drag/drop effects are allowed. The output argument is also an instance of the DragDropEffects enumeration specifying which effect has been executed.

The DragDropEffects are easy to provide, but the IDataObject needs a bit more work. Fortunately we've already seen the procedure to get this interface twice. We can once again use the GetUIObjectOf method (this method is really very useful). Notice that when you are dragging multiple items the ItemDrag event will only be raised once, so you'll have to check which items are selected to get the right IDataObject.

public ShellDll.IDataObject GetIDataObject(ShellItem[] items, 
                            out IntPtr dataObjectPtr)
{
    ShellItem parent = 
        items[0].ParentItem != null ? items[0].ParentItem : items[0];

    IntPtr[] pidls = new IntPtr[items.Length];
    for (int i = 0; i < items.Length; i++)
        pidls[i] = items[i].PIDLRel.Ptr;

    if (parent.ShellFolder.GetUIObjectOf(
            IntPtr.Zero,
            (uint)pidls.Length,
            pidls,
            ref ShellAPI.IID_IDataObject,
            IntPtr.Zero,
            out dataObjectPtr) == ShellAPI.S_OK)
    {
        ShellDll.IDataObject dataObj =
            (ShellDll.IDataObject)
                Marshal.GetTypedObjectForIUnknown(
                    dataObjectPtr, typeof(ShellDll.IDataObject));

        return dataObj;
    }
    else
    {
        dataObjectPtr = IntPtr.Zero;
        return null;
    }
}

Once the drag has been initialized, your IDropSource interface will receive two messages concerning the drag. The first one is QueryContinueDrag, which asks what to do with a certain situation, either perform the drop, cancel it, or continue dragging. You get some information to determine what to do. You'll get a bool whether the escape key has been pressed, if so the operation has to be cancelled. You also get the state of the modifier keys and the mouse buttons. This is where you have to check whether to continue the drag or perform the drop. If the mouse button which initialized the drag is still pressed, continue the drag, otherwise perform the drop. Then this is what the method is going to look like.

public int QueryContinueDrag(bool fEscapePressed, ShellAPI.MK grfKeyState)
{
    if (fEscapePressed)
        return ShellAPI.DRAGDROP_S_CANCEL;
    else
    {
        if ((startButton & MouseButtons.Left) != 0 && 
            (grfKeyState & ShellAPI.MK.LBUTTON) == 0)
            return ShellAPI.DRAGDROP_S_DROP;
        else if ((startButton & MouseButtons.Right) != 0 && 
                 (grfKeyState & ShellAPI.MK.RBUTTON) == 0)
            return ShellAPI.DRAGDROP_S_DROP;
        else
            return ShellAPI.S_OK;
    }
}

The other message your interface will receive is GiveFeedback. You will get the current DragDropEffect which applies to the dragged object. This message will allow you to change the Cursor to match this specific DragDropEffect. Because the normal Cursors are the ones we need, this method will only have one line in it. The Shell provides us with an option to just use the standard Cursors for drag/drop operations, which is exactly what we want. So the method will look like this.

public int GiveFeedback(DragDropEffects dwEffect)
{
    return ShellAPI.DRAGDROP_S_USEDEFAULTCURSORS;
}

Well, we're done. That was all you have to do for the drag operations. I haven't said anything about the browsing part of my control just because it would take too much time and make this article too long. If anyone really wants to know, I might write another article about this.

Plug-ins

What kind of plug-ins

With the 1.3 update I added the option to add plug-ins to the program. These plug-ins are to gain extra information about files and folders. At this moment I have two different plug-ins and one is in the making. The first of the two is a plug-ins is a plug-in to retrieve extra columns for the details view of the ListView. Without plug-ins you only have the "Name" column which is just to little for a details view. With the demo project I have added a plug-in of this kind which addes the "size", "date created" and "date modified" columns. The second plug-in is a bit more advanced, it is a special view for the ListView. In the demo project I have added a demo plug-in of this kind which will add the "Image View" to the ListViews view options. If you select this view, you'll get to see a preview of images once you select them. See the following picture to get a better idea of what I mean.

Sample image

How to make them

To make your own plug-ins, you'll have to make a project with a reference to the FileBrowser.dll. Once you've done this there are two interface for the plug-ins I mentioned. One is the IColumnPlugin, the other is the IViewPlugin. You have to make a public class which implement one or even both of these interfaces. After you've done that build your project as a Class Library which will create a DLL-file. Add this dll-file to a folder named "plugins" in the folder where you start your program and start your program. Now your plug-in should be loaded and you can use it. For a demo project see the plug-in demo project you can download above.

What do all the methods do

Before you can build your own plug-in however, you obviously need to know what every method of both interfaces do and when they are called. So I'll give a short explanation of what they do. Both interfaces implement the basic IBrowserPlugin interface which I will explain first.

  • IBrowserPlugin

    Name The name of the plug-in.
    Info A short description of the plug-in

    These properties aren't used at the moment, but I will use them later to list the loaded plug-ins and to allow the user to select which plug-in to use.

  • IColumnPlugin

    ColumnNames An array with the names of all the columns this plug-in provides
    GetAlignment Returns the HorizontalAlignment of a specific column
    GetFolderInfo Returns the information for a specific column for a folder
    GetFileInfo Returns the information for a specific column for a file

    The GetFolderInfo and GetFileInfo methods are called when the current directory changes, they return the info which will be put in the columns for the plug-in. The plug-in will get two arguments when this method is called. Either an IDirInfoProvider if the item is a directory or IFileInfoProvider when the item is a file. These interface will provide a structure with info about the file or folder and for files it will also provide a Stream to that file. The second argument is the ShellItem of the specific item to provide the info for. With these two arguments the plug-in should retrieve the needed info and provide a string for the column. To get a better idea of the possibilities see the demo project.

  • IViewPlugin

    ViewName The name to show when selecting the ListView view options
    ViewControl The Control which will be showed when the view is selected
    FolderSelected Will be called when a folder is selected
    FileSelected Will be called when a file is selected
    Reset Will be called when a new directory is opened

    The ViewControl can be about anything you like, so you can make quite a variety of view plug-ins. Just make sure the Control is initialized when the plug-ins constructor is called or you'll get cross-thread problems. The FolderSelected and FileSelected methods will be called when an item is selected and have the same arguments as the GetFolderInfo and GetFileInfo methods. In my demo plug-in I use the Stream I got from the IFileInfoProvider to read a picture and show it on the Control.

If you need any more info on the plug-ins please post a message below and I will try to answer as soon as possible. In the next update I hope to include a third plug-in with which you can add commands to the ContextMenu. For now you can experiment with these two.

Credits

While I was writing my program I used a lot of sources on the Internet, because there is just so much written for this subject, so I can't give you all the sites which contributed to my work, but I'll give you the main articles which are the ones I couldn't have done without.

Improvements

There is definitely room for improvement in my program. The first thing I need to do is add much more comment to my code, because it almost hasn't got any, after that a few main things need to be polished:

  • Make a nice drag image when dragging from my control
  • Improve speed when browsing folders with many objects
  • Add more menu items to the standard ContextMenu of the ListView
  • Make a undo and redo option
  • And probably a lot more which I can't think of right now, any other ideas from readers would obviously be welcome as well

History

08/23/2006: V1.3.3

  • Added entry-point to SHNotifyRegister and SHNotifyDeregister in order to prevent any problems when calling them

08/22/2006: V1.3.2

  • Updated PIDL Class, Added IL-functions
  • Added demo project with only a TreeView to browse folders

08/21/2006: V1.3

  • Added plug-in feature to the Browser
  • Updated the demo project with a dual-pane browser
  • Added demo project for plug-ins
  • Added Back and Forward buttons to the navigation bar
  • Added new properties and public methods to the Browser
  • Added Tooltips for ListViewItems (only files for now)
  • Added shortcut for creating a new folder (Ctrl + N)
  • Bug fix for navigation bar when entering an address
  • Bug fix for drop operation
  • Created ShellHelper class with methods which are used often
  • Improved Browser startup
  • Other small bug fixes
  • Updated article with "Using the Control" and "Plug-ins" sections

08/14/2006: V1.2

  • Added "New" menu to the ListView's ContextMenu
  • Improvement in the update thread
  • Small bug fixes

08/11/2006: V1.1

  • Memory leak fixed in the update thread
  • Added ShellNotify functions
  • Large speed improvement when browsing folders with many items
  • Added dialogs when media is not inserted
  • Navigationbar bug fixes
  • Added rename function
  • Rewritten PIDL class to use Shell32 imported methods
  • Added "Paste" and "Paste Shortcut" menus to ListView ContextMenu
  • Added "Name" column to details view
  • Other small bug fixes

08/05/2006: V1.0

  • Initial version of this article
  • Initial version of the program

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here