Contents
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 ContextMenu
s 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.
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.
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.
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.
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.
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 ShellItem s, 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:
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 Object
s 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.
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.
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.
These classes provide a wrapper around the drag/drop operations and the ContextMenu
s for the control.
BrowserTVContextMenuWrapper and BrowserLVContextMenuWrapper |
Provide ContextMenu s 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 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));
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
.
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 ContextMenu
s to work. My program also adds the Collapse and Expand MenuItem
s on a TreeNode
ContextMenu
, like Windows Explorer does. It will also raise an event for showing the ContextMenuItem
s help String
when the item is being hovered over. Just check my code if you need to know how to do this.
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 |
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.
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.
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 IDataObject
s 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 Cursor
s 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 Cursor
s 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.
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 ListView
s 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.
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.
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.
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.
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
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
Tooltip
s for ListViewItem
s (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