Introduction
All the source code I could find to implement a Drag and Drop to a Windows folder was using Shell Extensions. Initially, I implemented it that way. And, it was working fine in all Windows versions except Vista. They have changed some internal interfaces and made my code not work, which prompted me to find another way of implementing it without depending on the Operating System too much.
I have used Visual Studio 2005 for development, and have tested this in Windows XP, Windows 2000, Windows 2003, and Vista.
Background
I have just concentrated on fetching the directory path where the drop is performed. Use your own methods to initiate the drag and handle it. I have used FileSystemWatcher
to implement this. Here is the working:
When a drag is initiated from your application, a temporary file is created in the system's temp directory. We need to identify this among the tons of files created there, so I have prefixed it with a constant which the application can detect. As it is being used for identification, make it in a unique pattern so that other files will not have that name. A FileSysemWatcher
will be set to the system's Temp folder to check if any file with this name is created there. When we initiate a drag, this file is created and the FileSystemWatcher
will catch it. We will set this file as the drag source so that Windows will recognize it as a genuine file drop and this file will be copied to the location where we need to download the original file. What I did next is to implement a FileSystemWatch
for the dropped file for all the logical drives in the system. If it is dropped in any Windows folder or the Desktop, it will be caught and the file system watch over the drives will be removed instantly. If the drop is canceled, then also the watch is canceled.
Using the code
First, we need to put the system's Temp folder on watch for the file we create when a drag is initiated. We need to do it in the starter thread itself, and these are the variables I have used in Program.cs to do it:
#region Variables
internal const string DRAG_SOURCE_PREFIX = "__DragNDrop__Temp__";
internal static object objDragItem;
private static FileSystemWatcher tempDirectoryWatcher;
public static Hashtable watchers = null;
#endregion
And in the Main()
, I have added the watch:
static void Main()
{
tempDirectoryWatcher = new FileSystemWatcher();
tempDirectoryWatcher.Path = Path.GetTempPath();
tempDirectoryWatcher.Filter =
string.Format("{0}*.tmp", DRAG_SOURCE_PREFIX);
tempDirectoryWatcher.NotifyFilter = NotifyFilters.FileName;
tempDirectoryWatcher.IncludeSubdirectories = false;
tempDirectoryWatcher.EnableRaisingEvents = true;
tempDirectoryWatcher.Created +=
new FileSystemEventHandler(TempDirectoryWatcherCreated);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new DragNDrop());
}
Here, the FIleSystemWatch
will be monitoring the temp folder for a file to be created with "DRAG_SOURCE_PREFIX" and with a ".tmp" extension.
Now in the event of such a file being created, we need to put all the logical drives in the system on watch.
private static void TempDirectoryWatcherCreated(object sender, FileSystemEventArgs e)
{
try
{
if (watchers == null)
{
int i = 0;
Hashtable tempWatchers = new Hashtable();
FileSystemWatcher watcher;
foreach (string driveName in Directory.GetLogicalDrives())
{
if (Directory.Exists(driveName))
{
watcher = new FileSystemWatcher();
watcher.Filter = string.Format("{0}*.tmp", DRAG_SOURCE_PREFIX);
watcher.NotifyFilter = NotifyFilters.FileName;
watcher.Created += new FileSystemEventHandler(FileWatcherCreated);
watcher.IncludeSubdirectories = true;
watcher.Path = driveName;
watcher.EnableRaisingEvents = true;
tempWatchers.Add("file_watcher" + i.ToString(), watcher);
i = i + 1;
}
}
watchers = tempWatchers;
tempWatchers = null;
}
}
catch (Exception ex)
{
}
}
The checking if the directory exists or not may sound absurd to you. But in an OS like Windows 2000, even if there is no floppy drive present, an "A:/" entry will be shown in My Computer. I've added it to avoid exceptions like this. Now, if a file is dropped with the drag prefix we have declared with the ".tmp" extension, it will be caught by the event handler.
I think we are done with Program.cs and will move further. So now, we need to handle the events we've created for all the FileSystemWatch
es which we've kept in the Hashtable
which monitors the creation of the file.
private static void FileWatcherCreated(object sender, FileSystemEventArgs e)
{
try
{
string dropedFilePath = e.FullPath;
if (dropedFilePath.Trim() != string.Empty && objDragItem != null)
{
string dropPath = dropedFilePath.Substring(0,
dropedFilePath.LastIndexOf('\\'));
if (File.Exists(dropedFilePath))
File.Delete(dropedFilePath);
MessageBox.Show(String.Format("{0} dropped to {1}",
objDragItem.ToString(),dropPath));
}
objDragItem = null;
}
catch (Exception ex)
{
}
}
We just need to remove the file name from the path here. I have also deleted the temp file dropped there.
One more method is required to remove the file system watch for all the logical drives to reduce system overhead.
public static void ClearFileWatchers()
{
try
{
if (watchers != null && watchers.Count > 0)
{
for (int i = 0; i < watchers.Count; i++)
{
((FileSystemWatcher)watchers["file_watcher" + i.ToString()]).Dispose();
}
watchers.Clear();
watchers = null;
}
}
catch (Exception ex)
{
}
}
Now, let's move on to the form or control from where you are going to initiate the drag. Before initiating the drag, the Hashtable
keeping the FileSystemWatcher
s is set to null
. You can do this by calling the ClearFileWatchers()
method we just wrote in Program.cs. This could be done in the MouseDown
event of the ListView
if any ListViewItem
is being clicked. And in MouseMove
(i.e., drag is initiated), we can create the temp file in the System's Temp folder and start the drag by calling the DoDragDrop()
method.
private void ItemsListView_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.None)
return;
if (itemDragStart && Program.objDragItem != null)
{
dragItemTempFileName = string.Format("{0}{1}{2}.tmp",
Path.GetTempPath(), Program.DRAG_SOURCE_PREFIX,
ItemsListView.SelectedItems[0].Text);
try
{
Util.CreateDragItemTempFile(dragItemTempFileName);
string[] fileList = new string[] { dragItemTempFileName };
DataObject fileDragData = new DataObject(DataFormats.FileDrop, fileList);
DoDragDrop(fileDragData, DragDropEffects.Move);
ClearDragData();
}
catch (Exception ex)
{
}
}
}
Here, Util
is a static class, and using the method CreateDragItemTempFIle()
, I am creating the temp file in the System Temp folder. In that method, please make sure that there is already no such file with the name existing in the Temp folder. Otherwise, it will be replaced and the Created
event won't be triggered for the FileSystemWatcher
watching the Temp folder. Better to delete the temp file if it exists earlier before creating the new one.
That will do you the job.
Points of interest
This will just do a temporary job of dropping the files. I still think I need to get back to shell extensions. If you intend to drop files to a single machine, this works fine. But, if you need to drop it to another machine in the network, this won't work. There, only Shell can help you.
History
- Posted: January 26th 2008.