6

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
    }
}
Riz
  • 6,746
  • 16
  • 67
  • 89
  • Have you tried this in the debugger? If you say the application hangs, you should be able to break into the debugger after a hang, take a look at the threads list and call-stacks and see why – Joe Aug 28 '22 at 17:03
  • Do you have a fully reproducing sample? PS: the initial VirtualFileDataObject has design issues (not sure it's related here): https://stackoverflow.com/a/33418624/403671 – Simon Mourier Aug 30 '22 at 16:45
  • @SimonMourier Here is the sample I am using https://github.com/dotriz/VirtualDragDrop - Drag doesn't drop to Windows explorer. But if we block the throw exception @ https://github.com/dotriz/VirtualDragDrop/blob/20e331ffcfcb31900406ee2f4229a49309caddf4/VirtualFileDataObject.cs#L219 then it drags files to apps like Slack – Riz Aug 31 '22 at 14:30
  • With your sample I can drag & drop from the blue buttons to Windows Explorer, it will create two files. What's the problem you have exactly? – Simon Mourier Aug 31 '22 at 20:46
  • Really @SimonMourier? I am on Windows 11 and I get `System.Runtime.InteropServices.COMException: 'Invalid FORMATETC structure (Exception from HRESULT: 0x80040064 (DV_E_FORMATETC))'` – Riz Sep 01 '22 at 09:25
  • @SimonMourier it seems it works when we run it without debugging mode. That's interesting. You were trying in debug mode or without it? – Riz Sep 01 '22 at 11:18
  • Works in all cases: release/debug windows 10/11 – Simon Mourier Sep 01 '22 at 11:26
  • That's weird. Is it something to worry about that in future this can not be debugged (atleast for me)? And since it still hangs when I drop file to Slack or any third-party app, can we change it that users can only drop it to Windows explorer? And slack etc do-not accept this drop. – Riz Sep 01 '22 at 12:39
  • I'm just posting both files to a Windows Explorer directory, I don't have Slack. It's possible that Slack gets a clipboard format that it wants for some reason and you've not put in the data object. Note I cannot debug your project, I can only run it in release or run in debug without debugging. Debugging just hangs my Visual Studio seriously, probably because of the VirtualFileDataObject which has issues, and is certainly something to worry about :-) – Simon Mourier Sep 01 '22 at 13:40
  • @SimonMourier you had similar issue few years back, its same solution. Was that debug able? – Riz Sep 02 '22 at 11:39
  • I guess so, yes. If you're interested (and I find some time) I can try to rework your sample and get rid of VirtualFileDataObject. – Simon Mourier Sep 02 '22 at 12:43
  • That would be awesome @SimonMourier – Riz Sep 02 '22 at 13:44
  • 1
    I have investigated. I don't think you can debug a .NET program (with today tools) with breakpoint or exceptions being throw when in a DoDragDrop call. Do DoDragDrop is a big modal loop that eats most messages sent to a window. Unfortunately the .NET debugger (in clr.dll) seems to also wait on this loop. 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 killing the program process. IMHO, you can only 1) ensure it never fails: wrap all interop code with try+catch{trace error} 2) don't use breakpoint in there – Simon Mourier Sep 03 '22 at 13:55
  • @SimonMourier please post these comments as an answer, so I can award it the bounty. Your valuable feedback will surely help others. – Riz Sep 06 '22 at 09:53

2 Answers2

1

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
          }
      }
    
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • do you have idea what changes can we do to data object, so that it only allows to drop files to Windows Explorer? And apps like skype, slack, gmail web etc do not accept the drop? This code is working fine when dropped to Windows explorer, but app hangs when dropped to any other apps ( which is not required in my case too ) – Riz Sep 06 '22 at 15:17
  • @Riz - Only allow to one app is impossible. What is exposed as drag & drop (a data object) is exposed to every app which is a drop target. I actually tried your sample (old version) with Slack and it seems to work, I dropped the 2 files on it. What is possible is the other apps require other formats (such as CF_HDROP). Note hang will happen if you debug your app and keep throwing in GetData – Simon Mourier Sep 06 '22 at 15:34
  • this is definitely possible. See this GIF https://jumpshare.com/v/3HAEL0Rl3ALbgRxIoJbt When I try to drag a file from an FTP client and drop to Slack, it doesn't allow it. But when same file is dropped to Windows Desktop it works and file downloading starts. My version works with Slack, but it hangs the app while it downloads the 12mb file. Perhaps you have very fast internet which downloads 12mb in no time and you don't feel that. But if you'll try bigger file, it will definitely show same behavior for you too. – Riz Sep 12 '22 at 10:31
  • No this is not possible, the source cannot know the target, the target cannot know the source. Only the target can decide it doesn't want a drop, and not because of the source, because of what's in the data object. In your test it's probably just that slack doesn't support or cannot read the formats that are in the drag&drop data object. – Simon Mourier Sep 12 '22 at 11:23
  • Yes, target decides it wants a drop or not. Slack supports zip file drops, but it didn't allow the drop of the file from ftp client. Drop of same file from Windows explorer will be allowed. Because I believe some information is added to Data object that tells the target that its not a local file but a remote streaming file. That information is what I am looking for : ) – Riz Sep 12 '22 at 15:06
0

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.

Alon Catz
  • 2,417
  • 1
  • 19
  • 23
  • `DownloadDataAsync` & `WriteAsync` are happening in `Action` which is triggered after drop. Can't use await there. – Riz Aug 30 '22 at 14:47
  • I don't follow. Which Action and which drop? – Alon Catz Aug 30 '22 at 14:51
  • This event `VirtualFile2_MouseButtonDown` triggers when user tries to drag a label to the Windows Explorer. This initiates the file drag to the explorer. Once user drops it, `VirtualFileDataObject` class handles the rest and calls the `StreamContents` Action – Riz Aug 30 '22 at 15:01
  • I see now that I misread your question. Sorry for the confusion – Alon Catz Aug 30 '22 at 17:38