0

I have implemented file drag-and-drop using Win32 API on a .NET Core app. The problem is, it works with some applications, and not with some other applications.

  • Works: Notepad++, Notepad, WordPad, MS Word, Gimp, 7Zip
  • Doesn't work: (Windows) File Explorer, Chrome, FireFox, VS Code, Libre Writer, Android Studio

Why is it so?

My "DataObject" implementing IDataObject returns this one FORMATETC for EnumFormatEtc(). In which I return only one FORMATETC.

new FORMATETC()
{
    cfFormat = CF_HDROP,
    ptd = IntPtr.Zero,
    dwAspect = DVASPECT.DVASPECT_ICON,
    lindex = -1,
    tymed = TYMED.TYMED_HGLOBAL
}

On QueryGetData(), if the format's tymed is TYMED_HGLOBAL, return S_OK meaning I am dragging a file. Otherwise return DV_E_TYMED meaning I don't have that type of data.

On GetData() or GetDataHere(), if format.cfFormat == CF_HDROP and format.tymed == TYMED.TYMED_HGLOBAL, I filled the medium like so:

medium = new STGMEDIUM();
medium.tymed = TYMED.TYMED_HGLOBAL;
medium.unionmember = mem;
medium.pUnkForRelease = IntPtr.Zero;

, where mem is the memory allocated with a DROPFILES like so:

IntPtr CreateDropFiles(string[] strFiles)
{
    var buffer = new MemoryStream();
    var df = new DROPFILES();
    IntPtr ipGlobal = IntPtr.Zero;
    int bufferLength;

    foreach(var file in strFiles)
    {
        var b = Encoding.Unicode.GetBytes(file);
        buffer.Write(b, 0, b.Length);
        buffer.WriteByte(0);
        buffer.WriteByte(0);
    }
    buffer.WriteByte(0);
    bufferLength = (int)buffer.Length;

    // Allocate and get pointer to global memory
    int intTotalLen = Marshal.SizeOf(df) + bufferLength;
    ipGlobal = Marshal.AllocHGlobal(intTotalLen);

    df.pFiles = Marshal.SizeOf(df);
    df.fWide = true;

    Marshal.StructureToPtr(df, ipGlobal, true);
    var ipNew = new IntPtr(ipGlobal.ToInt64() + Marshal.SizeOf(df));
    Marshal.Copy(buffer.ToArray(), 0, ipNew, bufferLength);

    return ipGlobal;
}

In contrast, file drag-and-drop to File Explorer or VS Code using WinForm's method like the following worked just fine.

var data = new System.Windows.Forms.DataObject(
    System.Windows.Forms.DataFormats.FileDrop,
    new string[] { file1, file2 });
dummy.DoDragDrop(data, System.Windows.Forms.DragDropEffects.Copy);
Damn Vegetables
  • 11,484
  • 13
  • 80
  • 135
  • 2
    Have you thought about security issues (UAC)? Two applications not running at the same UAC level cannot communicate (using D&D). So for example, if you run Explorer a standard way (not admin), you cannot D&D with an app running as admin. – Simon Mourier Sep 05 '19 at 21:34
  • Do some debugging, see which of your methods are being called when you try to drag to those apps, and what they're asking for. – Jonathan Potter Sep 05 '19 at 22:36
  • 2
    Not all apps handle `CF_HDROP`. To support a wide range of applications, implement as many [Shell Clipboard Formats](https://learn.microsoft.com/en-us/windows/win32/shell/clipboard) as feasible. The more formats you implement, the more apps you can exchange data with. That said, `DVASPECT_ICON` is the wrong `dwAspect` for `CF_HDROP`, use `DVASPECT_CONTENT`. And your `buffer` is missing a byte, it needs to terminate the string list with a 2-byte null Unicode character, but you are terminating it with a single null byte. You need 2 null bytes, just like at the end of each string. – Remy Lebeau Sep 05 '19 at 23:26
  • If you are still having trouble, I suggest looking at the [source code for `System.Windows.Forms.DataObject`](https://referencesource.microsoft.com/#PresentationCore/Core/CSharp/system/windows/DataObject.cs) and see how exactly it implements `System.Windows.Forms.DataFormats.FileDrop`. – Remy Lebeau Sep 05 '19 at 23:26

0 Answers0