C# WPF - DragDrop a remote file to Windows Explorer

Viewed 248

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

  • When we drag it on Windows explorer it gives error in DEBUG mode but works fine when we run exe directly. What could be issue?

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

  • If we drag the file to an application like Slack, it hangs while file is downloading. But works fine (if we run exe directly) when file is dropped on to Windows Explorer.

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
    }
}
2 Answers

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
          }
      }
    

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.

Related