views:

205

answers:

2

I am trying to drag an item out into the explorer.
The item should result in a file download, so I've used an example I've found on the web to download the file using a CustomDataObject that calls an event when he actually needs the stream, and then my application does the heavy lifting and performs the download.
It's been working just fine in a similar Clipboard operation.

The actual file download also causes some UI changes in my application. Mostly a "working" icon changing on the form, and also a popup balloon in case of an error.

In the clipboard operation I just used InvokeRequired and BeginInvoke when needed, to make sure those UI changes happen on the main thread. In the drag operation, the UI thread is waiting for the return from the DoDragDrop, while the event being raised by the CustomDataObject is being called on a different thread. When I try to call BeginInvoke or Invoke the UI thread is still waiting, and I can't finish the drop.

Is there some sample, or a recommended best practice, on how to allow cross-application drag n drop, while accessing the UI of the source application?

UPDATE

here is the original CodeProject article with the DataObjectEx I modified for my own use. I just changed the GetFileContents method to call a virtual method which returns a Stream containing the file data, inherited from the class, and overridden that virtual method to get the file from the web. The problem arose when I wanted to change stuff in the UI, while getting the file. As I said earlier - the main UI thread is still "stuck" at the DoDragDrop method call, so I can't invoke it on time to do the UI changes needed by the worker thread before and after downloading the file.

+1  A: 

If this is a standard WinForms application, then all you really need to do in your application is to add event handlers in your Form for DragEnter and DragDrop.

Inside of DragEnter, you'll want to check the type of object to make sure it is a filename:

private void MyForm_DragEnter(object sender, DragEventArgs e)
{
    if (e.Data.GetDataPresent(DataFormats.FileDrop))
    {
        string[] files = e.Data.GetData(DataFormats.FileDrop) as string[];
        if (files != null)
        {
            // Do additional checks here if needed, like check extensions
            e.Effect = DragDropEffects.Copy;
            return;
        }
    }

    e.Effect = DragDropEffects.None;
}

Then in your DragDrop handler, I would simply store off the file names, and then activate a timer. This allows DragDrop to return immediately, so that the other application (in your example, Windows Explorer) does not hang while you do any processing on the file which may take time. The Drag Source will not return until DragDrop finishes.

private void MyForm_DragDrop(object sender, DragEventArgs e)
{
    string[] files = e.Data.GetData(DataFormats.FileDrop) as string[];
    if (files != null)
    {
        _filesToProcess.Text = files[0];  // Assuming this is declared at the Form level

        // Schedule a timer to fire in a few miliseconds as a simple asynchronous method
        _DragDropTimer.Interval = 50;
        _DragDropTimer.Enabled = true;
        _DragDropTimer.Start();
        Activate();  // Activates the form and gives it focus
    }
}
Nick
+1  A: 

I had the same issue and found that System.Windows.Forms.Control.DoDragDrop ignored my Form's implementation of COM's IAsyncOperation, instead using the WinForm's DataObject implementation of IDataObject internally. Unfortunately WinForm's DataObject class does not implement IAsyncOperation.

So I used this project's VirtualFileDataObject implementation of IAsyncOperation, IDataObject, calling VirtualFileDataObject.DoDragDrop instead of Control.DoDragDrop. I set a VirtualFileDataObject.FileDescriptor.StreamContents to a delegate where I Invoke onto the UI thread to report progress while downloading the file.

Rich Frank