Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

Async Drag 'n Drop or Drag 'n Drop from external server/device

5.00/5 (3 votes)
19 Nov 2011CPOL2 min read 22K   467  
Drag 'n Drop asynchronous files from an external server or device

Introduction

This article is about item dragging from a ListView in Windows application with files from an external device or Server, which are not stored on an accessible machine. It provides a class, which files on the drag 'n drop operation downloaded at first, before they are moved.

Background

I have written a Windows application which displayed in a ListView files on an external device. The requirement was, moves the files by Drag ‘n Drop from device to the local machine. My first try was, download all selected files from device, before calling the DoDragDrop method in the ListView_ItemDrag event. That was successful for one or two small files, but not for more or larger files. On moving of larger or more files, the download was slower as the user has pressed the mouse key. In this case, the Drag ‘n Drop operation was cancelled by Windows.

I have found in the web two examples which work with a lot of COMInterop code. I thought that must be easier.

Using the Code

At first, write a class like my FileDragDropHelper. The call must be inherited from System.Windows.Forms.DataObject. As the next step, override the method GetDataPresent. Please don’t forget to call the base method GetDataPresent after your action.

C#
public class FileDragDropHelper : DataObject
{
    private int _downloadIndex;
    private readonly Action<string> _downloadAction;

    /// <summary>
    /// Initializes a new instance of the <see cref="FileDragDropHelper"/> class.
    /// </summary>
    /// <param name="downloadAction">The action delegate which handles 
    /// the download of the source file to the target path on local machine 
    /// or mapped server path.</param>
    public FileDragDropHelper(Action<string> downloadAction)
    {
        if (downloadAction == null)
            throw new ArgumentNullException("downloadAction");

            _downloadAction = downloadAction;
        }

    /// <summary>
    /// Determines whether data stored in this 
    /// <see cref="T:System.Windows.Forms.DataObject"/> is associated with, 
    /// or can be converted to, the specified format.
    /// </summary>
    /// <param name="format">The format to check for. 
    /// See <see cref="T:System.Windows.Forms.DataFormats"/> 
    /// for predefined formats.</param>      
    /// <returns>
    /// true if data stored in this <see cref="T:System.Windows.Forms.DataObject"/> 
    /// is associated with, or can be converted to, 
    /// the specified format; otherwise, false.
    /// </returns>

    public override bool GetDataPresent(string format)
        {
        StringCollection fileDropList = GetFileDropList();

        if (fileDropList.Count > _downloadIndex)
        {
            string fileName = Path.GetFileName(fileDropList[_downloadIndex]);

            if (string.IsNullOrEmpty(fileName))
                return false;
                    _downloadAction(fileDropList[_downloadIndex]);
                    _downloadIndex++;
            }

        return base.GetDataPresent(format);
        }
    }

The 3rd step is the using of our new drag drop helper. Add a ListView control to your Windows Form control. Fill that with your list item. In my case, there are two web pages. Bind the ItemDrag event of the ListView with the Form-Designer. In the event handler, iterate over the selected ListView items and fill the item text into a generic Dictionary<string, string>, where the key is the target file in a temporary folder and the value is the source Uri. For the drag-drop operation you must specify that the target file is stored in a temporary folder. In addition, fill some temporary files in a StringCollection object.

C#
private void listView1_ItemDrag(object sender, ItemDragEventArgs e)
{
    if(listView1.SelectedItems.Count == 0)

        return;

StringCollection temporaryFiles = new StringCollection();

IDictionary<string, string> sourceFiles = 
    new Dictionary<string, string>(listView1.SelectedItems.Count);

foreach (ListViewItem item in listView1.SelectedItems)
{
    if (string.IsNullOrWhiteSpace(item.Text))
        continue;
    string fileName = Path.GetFileName(item.Text);

    if(string.IsNullOrWhiteSpace(fileName))
        continue;

    // Create a temporary file with the source file name in the temp folder.
    // Also required for the Windows Drag 'n Drop engine.
    string targetFilePath = Path.Combine(Path.GetTempPath(), fileName);

    temporaryFiles.Add(targetFilePath);

    // Required! 
    // Add the source file to the source files list.
    // The source files list is needed for file download.

    sourceFiles.Add(targetFilePath, item.Text);
}

FileDragDropHelper data = new FileDragDropHelper(file =>
{
    // This lamda expression is equals with a method MethodName
    // (string file) {// do something}
    KeyValuePair<string, string> filePair = sourceFiles.FirstOrDefault
                (fi => string.Equals(fi.Key, file));

    if (string.IsNullOrWhiteSpace(filePair.Key) || 
        string.IsNullOrWhiteSpace(filePair.Value))
        return;

    // Do not download async in this async context
    WebRequest request = WebRequest.CreateDefault(new Uri(filePair.Value));

    using(WebResponse response = request.GetResponse())
        {
        // read response stream
        using(Stream readStream = response.GetResponseStream())
                {
            if (readStream == null || !readStream.CanRead)

                return;

            // write stream to file of the current temporary files
            using(FileStream writeStream = new FileStream
        (filePair.Key, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite))
                        {
                byte[] buffer = new byte[16384];

                for (int index = readStream.Read(buffer, 0, buffer.Length); 
        index > 0; index = readStream.Read(buffer, 0, buffer.Length))
                                    writeStream.Write(buffer, 0, index);
                        }
                    }
                }
            });

            data.SetFileDropList(temporaryFiles);

            DoDragDrop(data, DragDropEffects.Copy);
        }

Points of Interest

The GetDataPresent method in the class FileDragDropHelper is the key for success.

History

  • 19th November, 2011: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)