4

I have a C# .NET 3.5 application that allows drag items from a tree and drop them to a folder as a file. Here is my code

String absolutePathToFile=...get absolute path
DataObject dataObject = new DataObject();
StringCollection paths = new StringCollection();
paths.Add(absolutePathToFile);
dataObject.SetFileDropList(paths);
DoDragDrop(dataObject, DragDropEffects.Copy);

This works quite well except when interacts with another C# application that accepts files via drag and drop, Another C# application has the following handler for DragOver

if ((e.Data is DataObject) && (e.Data as DataObject).ContainsFileDropList())
{
    e.Effect = DragDropEffects.Copy;
}

The block is never executed since e.Data is __ComObject not DataObject. Interestingly when I drag a file from a folder over second application it sees it as DataObject.

How to make DataObject from first C# application appear as DataObject in second C# application?

alecd4
  • 61
  • 1
  • 3
  • Try casting e.Data to a DataObject. For example: `DataObject obj = e.Data as DataObject; if (obj != null) { //Your code }` – fardjad Apr 23 '11 at 14:13
  • @fardjad if `(e.Data is DataObject)` is false, then you try to safe cast e.Data to DataObject, it's going to be null. – Bala R Apr 23 '11 at 15:25
  • e.Data is a COM object and `In the Microsoft .NET Framework, the GetType.Type operator for a COM object will return the System.__ComObject class. In some coding scenarios, you may have to know the specific class for an object.` [ http://support.microsoft.com/kb/320523 ] Certainly you should cast this e.Data to a DataObject or another class. – fardjad Apr 23 '11 at 21:57
  • Let me make it clear: this second application that receives files is a foreign application that we need to integrate with. I cannot modify it. I used Reflector to extract the code then built test app. The test app shows that application sees DataObject when I drag a file from Explorer, but when I initiate drag from C# application as above it sees __ComObject. More, local drag-drop within a C# application returns DataObject from e.Data, while drag-drop across process boundaries produces __ComObject. Drag-drop from Explorer and native apps froduces DataObject. I am trying to – alecd4 Apr 23 '11 at 23:11

1 Answers1

2

I decided to implement IDataObject using .NET Interop starting from IDataObject implementation from here. Then I defined DROPFILES structure

[StructLayoutAttribute(LayoutKind.Sequential)]
internal struct _DROPFILES
{
    public Int32 pFiles;
    public Int32 X;
    public Int32 Y;
    public bool fNC;
    public bool fWide;
}

and implement the code that fills all OLE structures. The function below returns IDataObject that I use in DoDragDrop:

DoDragDrop(GetDataObject(new String[] { file name }), DragDropEffects.Copy);

DataObject2 GetDataObject(String[] strFiles)
{
    byte[] bData;
    _DROPFILES df = new _DROPFILES();
    int intChar, intFile, intDataLen, intPos;
    IntPtr ipGlobal = IntPtr.Zero;

    // Calculate total data length
    intDataLen = 0;
    for (intFile = 0; intFile <= strFiles.GetUpperBound(0);intFile++)
    {
        intDataLen += strFiles[intFile].Length + 1;
    }
    // Terminating double zero
    intDataLen++;

    bData = new Byte[intDataLen];
    intPos = 0;

    // Build null terminated list of files
    for (intFile = 0; intFile <= strFiles.GetUpperBound(0); intFile++)
    {
        for (intChar = 0; intChar < strFiles[intFile].Length;intChar++)
        {
            bData[intPos++] = (byte)strFiles[intFile][intChar];
        }
        bData[intPos++] = 0;
    }
    // Terminating double zero
    bData[intPos++] = 0;

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

    if (ipGlobal == IntPtr.Zero)
    {
        return null;
    }

    // Build DROPFILES structure in global memory.
    df.pFiles = Marshal.SizeOf(df);
    df.fWide = false;
    Marshal.StructureToPtr(df, ipGlobal, true);
    IntPtr ipNew = new IntPtr(ipGlobal.ToInt32() + Marshal.SizeOf(df));
    Marshal.Copy(bData, 0, ipNew, intDataLen);

    short CF_HDROP = 15;
    System.Runtime.InteropServices.ComTypes.FORMATETC formatEtc;
    System.Runtime.InteropServices.ComTypes.STGMEDIUM stgMedium;

    formatEtc = new System.Runtime.InteropServices.ComTypes.FORMATETC();
    formatEtc.cfFormat = CF_HDROP;
    formatEtc.dwAspect = System.Runtime.InteropServices.ComTypes.DVASPECT.DVASPECT_CONTENT;
    formatEtc.lindex = -1;
    formatEtc.tymed = System.Runtime.InteropServices.ComTypes.TYMED.TYMED_HGLOBAL;

    stgMedium = new System.Runtime.InteropServices.ComTypes.STGMEDIUM();
    stgMedium.unionmember = ipGlobal;
    stgMedium.tymed = System.Runtime.InteropServices.ComTypes.TYMED.TYMED_HGLOBAL;
    DataObject2 dobj = new DataObject2();
    dobj.SetData(ref formatEtc, ref stgMedium, false);
    return dobj;

}

With new code second application sees DataObject in e.Data and I can drag-drop file to any application. Except now Explorer doesn't accept files. Do I miss something in my implementation?

Amar Palsapure
  • 9,590
  • 1
  • 27
  • 46
alecd4
  • 61
  • 1
  • 3
  • My app was 64-bit. It did not work. After some tracing, I found that the code crashed at `IntPtr ipNew = new IntPtr(ipGlobal.ToInt32() + Marshal.SizeOf(df));` with an overflow. I changed `ToInt32()` to `ToInt64()`, and then it worked. – Damn Vegetables Sep 05 '19 at 17:18