Tuesday, January 22, 2008

Drag and Drop files from Windows Explorer to Windows Form

How to open files dropped from Explorer to a Windows Form

To Download source files
Mail us at : codes.jinesh@gmail.com

Introduction
If we have a Windows Forms application which has File -> Open functionality, adding a few lines of code allows the user to open files dragged from the Windows Explorer. This article describes how to add this feature to your application.

Adding Drag-and-Drop capability to an existing Windows Forms application
Suppose we have simple image viewer program (demo project DropFiles). When tje user selects

File -> Open, it shows the 'Open File' dialog:

private void mnuFileOpen_Click(object sender, System.EventArgs e)
{
// Show Open File dialog
OpenFileDialog openDlg = new OpenFileDialog();
openDlg.Filter = "Jpeg files (*.jpg)*.jpgBitmap files (*.bmp)"
+ *.bmpAll Files (*.*)*.*";
openDlg.FileName = "" ;
openDlg.CheckFileExists = true;
openDlg.CheckPathExists = true;
if ( openDlg.ShowDialog() != DialogResult.OK )
return;
OpenFile(openDlg.FileName);
}


The function OpenFile loads the image file to a class member Bitmap m_Bitmap, and the Paint event handler shows this bitmap on the screen:

private Bitmap m_Bitmap;
private void OpenFile(string sFile)
{
Bitmap bmp;
try
{
// load bitmap from file
bmp = (Bitmap)Bitmap.FromFile(sFile, false);
if ( bmp != null )
{
// Keep bitmap in class member
m_Bitmap = (Bitmap) bmp.Clone();
// Set Autoscroll properties
this.AutoScroll = true;
this.AutoScrollMinSize = new Size (
m_Bitmap.Width,
m_Bitmap.Height);

// draw image
Invalidate();
}
}
catch (Exception ex)
{
MessageBox.Show(
this,
ex.Message,
"Error loading from file");
}
}
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
if ( m_Bitmap != null )
{
e.Graphics.DrawImage(
m_Bitmap,
new Rectangle(this.AutoScrollPosition.X,
this.AutoScrollPosition.Y,
m_Bitmap.Width,
m_Bitmap.Height));
}
}


To open files dropped from the Windows Explorer we need to set the form AllowDrop property to true. After this add the DragEnter and DragDrop event handlers.
DragEnter handler is simple - program accepts only files:

private void Form1_DragEnter(object sender, System.Windows.Forms.DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
e.Effect = DragDropEffects.Copy;
else
e.Effect = DragDropEffects.None;
}


The DragEventArgs.GetData function returns an IDataObject interface that contains the data associated with this DragDrop event. When a file or files are dropped from Explorer to the form, IDataObject contains an array of file names. If one file is dropped, the array contains one element. All the time the DragDrop event handler is working, the Windows Explorer instance is not responding. Therefore, we need to call the OpenFile function asynchronously. This is done using the BeginInvoke function and a delegate pointing to OpenFile. It is a good idea also to move the form to the foreground after dropping file to it.

private delegate void DelegateOpenFile(String s); // type
private DelegateOpenFile m_DelegateOpenFile; // instance
private void Form1_Load(object sender, System.EventArgs e)
{
// create delegate used for asynchronous call
m_DelegateOpenFile = new DelegateOpenFile(this.OpenFile);
}
private void Form1_DragDrop(object sender, System.Windows.Forms.DragEventArgs e)
{
try
{
Array a = (Array)e.Data.GetData(DataFormats.FileDrop);
if ( a != null )
{
// Extract string from first array element
// (ignore all files except first if number of files are dropped).
string s = a.GetValue(0).ToString();
// Call OpenFile asynchronously.
// Explorer instance from which file is dropped is not responding
// all the time when DragDrop handler is active, so we need to return
// immidiately (especially if OpenFile shows MessageBox).
this.BeginInvoke(m_DelegateOpenFile, new Object[] {s});
this.Activate(); // in the case Explorer overlaps this form
}
}
catch (Exception ex)
{
Trace.WriteLine("Error in DragDrop function: " + ex.Message);
// don't show MessageBox here - Explorer is waiting !
}
}


This is all we need to implement Drag-and-Drop capability in Windows Forms application. However, good programming style requires writing the wrapper class which encapsulates this feature.

Using the DragDropManager class
The second demo project DropFiles1 opens files dropped from Explorer using helper class DragDropManager. The project is very simple - it allows you to open one or more files and shows the file names in listbox. It has mnuFileOpen_Click function which shows Open File dialog and calls OpenFiles(Array a) passing array of file names selected by user.

Form that uses DragDropManager should implement IDropFileTarget interface defined in the same file as DragDropManager:

public interface IDropFileTarget
{
void OpenFiles(System.Array a);
}


So, main form is derived from IDropFileTarget:

public class Form1 : System.Windows.Forms.Form, IDropFileTarget
and has OpenFiles function which opens files selected from File - Open dialog or dropped from Windows Explorer:

public void OpenFiles(Array a)
{
// If our application can open only one file, we can process
// here only first element of array or give some error message
// in the case array contains more than one file.
//
// In this sample I allow to open number of files showing
// their names in list box.
string sError = "";
string sFile;
lstFiles.Items.Clear();
// process all files in array
for ( int i = 0; i < sfile =" a.GetValue(i).ToString();" info =" new"> 0 )
MessageBox.Show(this, sError, "Open File Error");
}

The Form has the member
private DragDropManager m_DragDropManager;

which is initialized in Load event handler:
private void Form1_Load(object sender, System.EventArgs e)
{
m_DragDropManager = new DragDropManager();
m_DragDropManager.Parent = this;
}


So, the steps that should be done for using DragDropManager class in Windows Form are:- Derive the form from IDropFileTarget interface;- Implement IDropFileTarget interface adding function OpenFiles(Array a);- Add member of DragDropManager type to the form;- Initialize it setting Parent property to form reference.

Class DragDropManager
This class keeps reference to owner form in class member and has delegate used for asynchronous call to the form:

private Form m_parent; // reference to owner form
private delegate void DelegateOpenFiles(Array a); // type
private DelegateOpenFiles m_DelegateOpenFiles; // instance


Initialization is done when client sets Parent property:

public Form Parent
{
set
{
m_parent = value; // keep reference to owner form
// Check if owner form implements IDropFileTarget interface
if ( ! ( m_parent is IDropFileTarget ) )
{
throw new Exception(
"DragDropManager: Parent form doesn't implement IDropFileTarget interface");
}
// create delegate used for asynchronous call
m_DelegateOpenFiles = new DelegateOpenFiles(((IDropFileTarget)m_parent).OpenFiles);
// ensure that owner form allows dropping
m_parent.AllowDrop = true;
// subscribe to owner form's drag-drop events
m_parent.DragEnter += new System.Windows.Forms.DragEventHandler(this.OnDragEnter);
m_parent.DragDrop += new System.Windows.Forms.DragEventHandler(this.OnDragDrop);
}
}
OnDragEnter and OnDragDrop are subscribed to owner form DragEnter and DragDrop events:
private void OnDragEnter(object sender, System.Windows.Forms.DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
e.Effect = DragDropEffects.Copy;
else
e.Effect = DragDropEffects.None;
}
private void OnDragDrop(object sender, System.Windows.Forms.DragEventArgs e)
{
try
{
Array a = (Array)e.Data.GetData(DataFormats.FileDrop);
if ( a != null )
{
// Call owner's OpenFiles asynchronously.
m_parent.BeginInvoke(m_DelegateOpenFiles, new Object[] {a});
m_parent.Activate(); // in the case Explorer overlaps owner form
}
}
catch (Exception ex)
{
Trace.WriteLine("Error in DragDropManager.OnDragDrop function: " + ex.Message);
}
}