![]() |
Desktop Development »
Files and Folders »
Utilities
Intermediate
License: The Code Project Open License (CPOL)
Dual Pane File ManagerBy Jeff GainesA dual pane file manager for Windows XP |
C#, .NET, WinXP, Visual Studio, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
I seem to spend a fair bit of time managing files on my PC and don't find Windows Explorer the easiest tool to use. The best file manager I have ever used came with a DOS program called Brooklyn Bridge, which sold me completely on the concept of dual panes. The files I want to copy/move are on the left, the place I want to move them to is on the right. Mark the files, press the right keys (no menus in those days) and job done. Unfortunately it is not Y2K compliant and I haven't been able to find an alternative that I can get on with.
I also like to keep notes about files, so I can remind myself what they are, and I also want the ability to search those notes for a file I “know” that I have somewhere. Although this is possible in Explorer, I prefer those notes to be in my database so I can back it up and re-use it after a re-install. Having tried several file managers, none seemed to do what I wanted so I took the plunge and wrote my own.
This project is the result, it is not the first attempt, it's probably not the one hundredth attempt, but it's the one that works and doesn't (seem to) leak memory.
There are several classes in .NET relating to file/folder handling, and the DriveInfo class has been added in version 2. However, there is still no .NET way to find the correct icon for any of these, or to find the proper mask for the icon – so that shares and system folders show up properly. There is also no .NET way to show the Explorer Context Menu. What I have done is to use the .NET classes as far as possible and fallen back on the API where I couldn't find a .NET solution.
At the top level, there is a Windows Form - JFileManager. This has the main menu and a Splitter Control. Within the Splitter Control sit two instances of a User Control called cFiler, the one on the left is the source and the one on the right the destination.
In turn, the cFiler control contains further controls - a cFilerTS, a cFilerTV and a cFilerLV. Each of these controls is self contained so the cFiler brings them all together and communicates between them.
I was anxious to ensure the program ran at a reasonable speed.
The first obvious bottleneck was getting the drive information which is used three times in each of the cFiler controls, making six times for the two controls. What I did was to set up all the information that was needed in a class called cJDriveInfoCollection which contains a HashTable in turn containing the CJDriveInfo. This is set up the first time it is called – and can be refreshed when needed. The class is static and I found it easier to use a HashTable than a Collections class.
The second bottleneck was setting the cSysTreeView to a new path. Since information is only added to a node when it is expanded, keeping a HashTable linking paths to nodes didn't seem practical. In the end, I decided to keep a HashTable of drives, so that the start node for any path could easily be found, and then work my way down the cSysTreeView finding the next node, expanding it (to get the folders added) and then continuing down. I ran some tests using the deepest nested folder I had but despite as much optimization as I was capable of, it takes just over two seconds to get to this node from the root node. I have left the test in (although the menu option is disabled) - if you want to try some tweaks to speed this up, I would be delighted to hear them!
When you close the TreeView pane, it is no longer kept up to date which speeds things up when you use it in that mode. It is updated when the TreeView pane is opened again.
Programming is a hobby for me. I have had a lot of help from various people over the years, generously and freely given, for which I am grateful.
I have acknowledged specific contributions in the source code.
I have tried to document the code as completely as possible, as it stands this is a complete application but there may be useful bits that can be pulled out and used in other projects.
This is the cJDriveInfo class:
using System;
using System.Collections;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
namespace JFileManager
{
/// <summary>
/// Provides additional information about drives
/// </summary>
public class cJDriveInfo
{
public string DisplayName = "";
public string Path = "";
public ObjectType ObjectType = ObjectType.DRIVE;
public DriveType DriveType = DriveType.Unknown;
public string TypeName = "";
public int IconIndex = 0;
public int SelectedIconIndex = 0;
public long TotalSize = 0L;
public long AvailableFreeSpace = 0L;
public Image Image = null;
public bool IsReady = false;
public int Mask;
public int StateMask;
public int State;
public bool HasChildren = false;
public string VolumeLabel = "";
public int Index;
public cJDriveInfo(string strPath)
: this(new DriveInfo(strPath))
{
}
public cJDriveInfo(DriveInfo drivInfo)
{
this.Path = drivInfo.RootDirectory.ToString();
this.DriveType = drivInfo.DriveType;
this.IsReady = drivInfo.IsReady;
this.GetSHFileInfo();
// We won't get this info if drive is not ready
try
{
this.TotalSize = drivInfo.TotalSize;
this.AvailableFreeSpace = drivInfo.AvailableFreeSpace;
// Display Name
this.VolumeLabel = drivInfo.VolumeLabel;
string strDrive = drivInfo.RootDirectory.ToString().Replace("\\", "");
this.DisplayName = this.VolumeLabel + " (" + strDrive + ")";
}
catch
{
this.TotalSize = 0L;
this.AvailableFreeSpace = 0L;
}
// Image Masks
cCommon.GetLVImageMask(this.Path, out this.Mask, out this.StateMask,
out this.State);
// Sub-folders
this.HasChildren = (cCommon.DirectorySubFolderCount(this.Path) > 0);
}
private void GetSHFileInfo()
{
SHFILEINFO shfi = new SHFILEINFO();
UInt32 uFlags = (UInt32)(SHGFI.SHGFI_ICON | SHGFI.SHGFI_SMALLICON |
SHGFI.SHGFI_TYPENAME | SHGFI.SHGFI_ADDOVERLAYS |
SHGFI.SHGFI_SYSICONINDEX | SHGFI.SHGFI_DISPLAYNAME);
IntPtr ipTemp = cCommon.SHGetFileInfo(this.Path, 0, out shfi,
Marshal.SizeOf(shfi), uFlags);
// Check result
if (ipTemp != IntPtr.Zero)
{
this.DisplayName = shfi.szDisplayName;
this.TypeName = shfi.szTypeName;
this.IconIndex = shfi.iIcon;
this.Image = cCommon.ImageFromIcon(shfi.hIcon);
}
if (shfi.hIcon != IntPtr.Zero)
cCommon.DestroyIcon(shfi.hIcon);
// Selected Icon
uFlags = (uint)(SHGFI.SHGFI_ICON | SHGFI.SHGFI_SMALLICON |
SHGFI.SHGFI_ADDOVERLAYS | SHGFI.SHGFI_SYSICONINDEX |
SHGFI.SHGFI_OPENICON);
ipTemp = cCommon.SHGetFileInfo(this.Path, 0, out shfi,
Marshal.SizeOf(shfi), uFlags);
if (ipTemp != IntPtr.Zero)
{
this.SelectedIconIndex = shfi.iIcon;
}
if (shfi.hIcon != IntPtr.Zero)
cCommon.DestroyIcon(shfi.hIcon);
}
}
}
As you can see, its properties are obtained partly from the inbuilt DriveInfo class and partly from the API.
Using the API means moving out of the shelter of .NET’s memory management. I learnt (not quickly I have to confess) to read the help that comes with Visual Studio, especially when using the API!
Early versions of this program had massive memory leaks and so I read the help file for every API call I used and found the following relating to SHGetFileInfo:
If SHGetFileInfo returns an icon handle in the hIcon member of the SHFILEINFO structure pointed to by psfi, you are responsible for freeing it with DestroyIcon when you no longer need it.
Whoops!
My fault, I really should read the help more closely. I hope that I have picked up all the memory issues now.
cJDirectoryInfo and cJFileInfo classes in the interest of optimization. The information is now set up directly in the cTNode and cLVItem classes. cFilerTS, cFilerTV and cFilerLV to be self contained and to sit in cFiler. try/catch block when reading LastWriteTime as it seems to have problems with some of my CD-Rs. WM_DEVICECHANGE is not propagated if you have CD AutoPlay off. I tried timers but was worried about resource issues. In the end, I decided to check removable devices when you hover over them (in the TreeView or ListView), I'd be interested in feedback on this approach. I've just realized that I don't update the DropDown with the fresh information, I will have a look at that for a later version. cUserFileAccessRights to indicate the user's access rights to folders (used in drag and drop). This class was written by Bruce Hatt here. It's an enormously useful class and has no equivalent in .NET.
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 3 Dec 2008 Editor: Deeksha Shenoy |
Copyright 2006 by Jeff Gaines Everything else Copyright © CodeProject, 1999-2009 Web09 | Advertise on the Code Project |