Riz
Riz

Reputation: 6982

C# WPF - DragDrop a remote file to Windows Explorer

I am working on a WPF application which shows a list of files stored on a remote server (just like dropbox). I want users to drag and drop them onto desktop or any folder. There are many questions posted related to this but none of them really give a complete solution.

Here is the complete code I am using https://github.com/dotriz/VirtualDragDrop/

Screenshot

This is a very simple task if file is stored on local system, but here file is on a remote server and need to be downloaded first.

The only article related to this, is 13 years old posted here https://dlaa.me/blog/post/9923072. It has few issues too, like

Invalid FORMATETC structure (Exception from HRESULT: 0x80040064 (DV_E_FORMATETC)

Here is the code used on MouseDown event of a label. It uses VirtualFileDataObject class from the link given above

private void VirtualFile2_MouseButtonDown(object sender, MouseButtonEventArgs e)
{
    var virtualFileDataObject = new VirtualFileDataObject();

    virtualFileDataObject.SetData(new VirtualFileDataObject.FileDescriptor[]
    {
        new VirtualFileDataObject.FileDescriptor
        {
            Name = "test.zip",
            ChangeTimeUtc = DateTime.Now.AddDays(-1),
            StreamContents = stream =>
                {
                    using(var webClient = new WebClient())
                    {
                        var data = webClient.DownloadData("https://somesite.com/test.zip");
                        stream.Write(data, 0, data.Length);
                    }

                }
        },
    });

    DoDragDropOrClipboardSetDataObject(e.ChangedButton, VirtualFile2, virtualFileDataObject, DragDropEffects.Copy);
}

private static void DoDragDropOrClipboardSetDataObject(MouseButton button, DependencyObject dragSource, VirtualFileDataObject virtualFileDataObject, DragDropEffects allowedEffects)
{
    try
    {
        VirtualFileDataObject.DoDragDrop(dragSource, virtualFileDataObject, allowedEffects);
    }
    catch (COMException)
    {
        // Failure; no way to recover
    }
}

Upvotes: 7

Views: 768

Answers (2)

Simon Mourier
Simon Mourier

Reputation: 138950

I can successfully use the provided reproduction program. It works at least on Windows 10, 11, x86 x64 in Release and Debug configuration, when ran without debugging it.

DV_E_FORMATETC (0x80040064 ) is just a standard error code that means the data object doesn't support the clipboard format that's requested. This error is common in copy/paste, drag&drop and clipboard operations.

What doesn't work at all is:

  • Putting a breakpoint in the Drag & Drop handling code, for example in void System.Runtime.InteropServices.ComTypes.IDataObject.GetData(ref FORMATETC format, out STGMEDIUM medium) while running in Debug mode.
  • Raising an exception in the Drag & Drop handling code when running in Debug mode.

I don't think you can debug a .NET program (with today tools) with breakpoints or exceptions being throw when in a DoDragDrop function call.

DoDragDrop is a big modal loop that eats most messages sent to a window. Unfortunately the .NET debugger facility or exception handler (in clr.dll) seems to also wait on this loop for some reason. So, hitting a breakpoint or handling an exception creates a hang in the program itself (and in the debugger too) that you can only break the dead lock by killing the debugged process the hard way (taskkill /im myprocess.exe /f command line for example).

So my recommendation is to:

  • avoid putting breakpoints in these portions of code

  • wrap these functions in try/catch block and use some non-intrusive trace mechanism, something like this:

      void System.Runtime.InteropServices.ComTypes.IDataObject.GetData(ref FORMATETC format, out STGMEDIUM medium)
      {
          try
          {
              ....
          }
          catch(Exception ex)
          {
              Trace.WriteLine("GetData Error: " + ex);
              throw; // rethrow as is
          }
      }
    

Upvotes: 1

Alon Catz
Alon Catz

Reputation: 2530

EDIT: This answer is wrong

I can explain why the UI hangs and how to solve it. There are two IO operations inside VirtualFile2_MouseButtonDown which you perform in a blocking way. This obviously blocks the UI thread until both operations finish.

  1. Change the signature to private async void VirtualFile2_MouseButtonDown
  2. Use await webClient.DownloadDataAsync
  3. Use await stream.WriteAsync

This way, VirtualFile2_MouseButtonDown will return on the first IO operation and continuation tasks will be scheduled on the UI thread when the async IO operation is ready.

Upvotes: 0

Related Questions