Trying to implement Drag and Drop gmail attachment

2020-07-17 16:34发布

问题:

I have been trying to implement drag and drop a gmail attachment from chrome into my application.

Its possible to drag the file from the email to the desktop and it create the attachment there so I know this must be possible.

I have been able to get it to read the file name, but when I read FileContents from the data object I get an internet shortcut with a link to the file.

Has anyone got this working before? the code at the moment is hard coded for a .txt file

The sample project can be downloaded from:

https://www.dropbox.com/s/jz4zde0mvgxzn1g/DragDropTest.zip?dl=0

My main DataObjectWrapper class is as follows:

there are too many characters to post all of it but the main method is:

public object GetDataNative(string format, bool autoConvert)
{
    switch (format)
    {
        case CFSTR_FILEDESCRIPTOR_A:

            IntPtr fileGroupDescriptorAPointer = IntPtr.Zero;
            try
            {
                //use the underlying IDataObject to get the FileGroupDescriptor as a MemoryStream
                MemoryStream fileGroupDescriptorStream = (MemoryStream)this.underlyingDataObject.GetData(CFSTR_FILEDESCRIPTOR_A, autoConvert);
                byte[] fileGroupDescriptorBytes = new byte[fileGroupDescriptorStream.Length];
                fileGroupDescriptorStream.Read(fileGroupDescriptorBytes, 0, fileGroupDescriptorBytes.Length);
                fileGroupDescriptorStream.Close();

                //copy the file group descriptor into unmanaged memory 
                fileGroupDescriptorAPointer = Marshal.AllocHGlobal(fileGroupDescriptorBytes.Length);
                Marshal.Copy(fileGroupDescriptorBytes, 0, fileGroupDescriptorAPointer, fileGroupDescriptorBytes.Length);

                //marshal the unmanaged memory to to FILEGROUPDESCRIPTORA struct
                object fileGroupDescriptorObject = Marshal.PtrToStructure(fileGroupDescriptorAPointer, typeof(NativeMethods.FILEGROUPDESCRIPTORA));
                NativeMethods.FILEGROUPDESCRIPTORA fileGroupDescriptor = (NativeMethods.FILEGROUPDESCRIPTORA)fileGroupDescriptorObject;

                //get the pointer to the first file descriptor
                IntPtr fileDescriptorPointer = (IntPtr)((int)fileGroupDescriptorAPointer + Marshal.SizeOf(fileGroupDescriptor.cItems));

                NativeMethods.FILEDESCRIPTORA[] fileDescriptors = new NativeMethods.FILEDESCRIPTORA[fileGroupDescriptor.cItems];

                //loop for the number of files acording to the file group descriptor
                for (int fileDescriptorIndex = 0; fileDescriptorIndex < fileGroupDescriptor.cItems; fileDescriptorIndex++)
                {

                    //marshal the pointer top the file descriptor as a FILEDESCRIPTORA struct and get the file name
                    NativeMethods.FILEDESCRIPTORA fileDescriptor = (NativeMethods.FILEDESCRIPTORA)Marshal.PtrToStructure(fileDescriptorPointer, typeof(NativeMethods.FILEDESCRIPTORA));
                    fileDescriptors[fileDescriptorIndex] = fileDescriptor;

                    //move the file descriptor pointer to the next file descriptor
                    fileDescriptorPointer = (IntPtr)((int)fileDescriptorPointer + Marshal.SizeOf(fileDescriptor));
                }

                fileGroupDescriptor.fgd = fileDescriptors;

                //return the array of filenames
                return fileGroupDescriptor;
            }
            finally
            {
                //free unmanaged memory pointer
                Marshal.FreeHGlobal(fileGroupDescriptorAPointer);
            }

        case CFSTR_FILEDESCRIPTOR_W:

            IntPtr fileGroupDescriptorWPointer = IntPtr.Zero;
            try
            {
                //use the underlying IDataObject to get the FileGroupDescriptorW as a MemoryStream
                MemoryStream fileGroupDescriptorStream = (MemoryStream)this.underlyingDataObject.GetData(CFSTR_FILEDESCRIPTOR_W);
                byte[] fileGroupDescriptorBytes = new byte[fileGroupDescriptorStream.Length];
                fileGroupDescriptorStream.Read(fileGroupDescriptorBytes, 0, fileGroupDescriptorBytes.Length);
                fileGroupDescriptorStream.Close();

                //copy the file group descriptor into unmanaged memory
                fileGroupDescriptorWPointer = Marshal.AllocHGlobal(fileGroupDescriptorBytes.Length);
                Marshal.Copy(fileGroupDescriptorBytes, 0, fileGroupDescriptorWPointer, fileGroupDescriptorBytes.Length);

                //marshal the unmanaged memory to to FILEGROUPDESCRIPTORW struct
                object fileGroupDescriptorObject = Marshal.PtrToStructure(fileGroupDescriptorWPointer, typeof(NativeMethods.FILEGROUPDESCRIPTORW));
                NativeMethods.FILEGROUPDESCRIPTORW fileGroupDescriptor = (NativeMethods.FILEGROUPDESCRIPTORW)fileGroupDescriptorObject;

                //get the pointer to the first file descriptor
                IntPtr fileDescriptorPointer = (IntPtr)((int)fileGroupDescriptorWPointer + Marshal.SizeOf(fileGroupDescriptor.cItems));

                NativeMethods.FILEDESCRIPTORW[] fileDescriptiors = new NativeMethods.FILEDESCRIPTORW[fileGroupDescriptor.cItems];

                //loop for the number of files acording to the file group descriptor
                for (int fileDescriptorIndex = 0; fileDescriptorIndex < fileGroupDescriptor.cItems; fileDescriptorIndex++)
                {
                    //marshal the pointer top the file descriptor as a FILEDESCRIPTORW struct and get the file name
                    NativeMethods.FILEDESCRIPTORW fileDescriptor = (NativeMethods.FILEDESCRIPTORW)Marshal.PtrToStructure(fileDescriptorPointer, typeof(NativeMethods.FILEDESCRIPTORW));
                    fileDescriptiors[fileDescriptorIndex] = fileDescriptor;

                    //move the file descriptor pointer to the next file descriptor
                    fileDescriptorPointer = (IntPtr)((int)fileDescriptorPointer + Marshal.SizeOf(fileDescriptor));
                }

                fileGroupDescriptor.fgd = fileDescriptiors;

                //return the array of filenames
                return fileGroupDescriptor;
            }
            finally
            {
                //free unmanaged memory pointer
                Marshal.FreeHGlobal(fileGroupDescriptorWPointer);
            }

        case CFSTR_FILECONTENTS:

            //override the default handling of FileContents which returns the
            //contents of the first file as a memory stream and instead return
            //a array of MemoryStreams containing the data to each file dropped

            //get the array of filenames which lets us know how many file contents exist
            string[] fileContentNames = (string[])this.GetData(CFSTR_FILEDESCRIPTOR_W);

            //create a MemoryStream array to store the file contents
            MemoryStream[] fileContents = new MemoryStream[fileContentNames.Length];

            //loop for the number of files acording to the file names
            for (int fileIndex = 0; fileIndex < fileContentNames.Length; fileIndex++)
            {
                //get the data at the file index and store in array
                fileContents[fileIndex] = this.GetData(format, fileIndex);
            }

            //return array of MemoryStreams containing file contents
            return fileContents;

        case CFSTR_INETURL_A:

            //use the underlying IDataObject to get the FileGroupDescriptorW as a MemoryStream
            MemoryStream UniformResourceLocatorStream = (MemoryStream)this.underlyingDataObject.GetData(CFSTR_INETURL_A);
            byte[] UniformResourceLocatorBytes = new byte[UniformResourceLocatorStream.Length];
            UniformResourceLocatorStream.Read(UniformResourceLocatorBytes, 0, UniformResourceLocatorBytes.Length);
            UniformResourceLocatorStream.Close();

            string url = null;

            if (UniformResourceLocatorBytes[1] == 0)
                url = Encoding.Unicode.GetString(UniformResourceLocatorBytes);
            else
                url = Encoding.ASCII.GetString(UniformResourceLocatorBytes);

            return url;

        case CFSTR_INETURL_W:

            //use the underlying IDataObject to get the FileGroupDescriptorW as a MemoryStream
            MemoryStream UniformResourceLocatorWStream = (MemoryStream)this.underlyingDataObject.GetData(CFSTR_INETURL_W);
            byte[] UniformResourceLocatorWBytes = new byte[UniformResourceLocatorWStream.Length];
            UniformResourceLocatorWStream.Read(UniformResourceLocatorWBytes, 0, UniformResourceLocatorWBytes.Length);
            UniformResourceLocatorWStream.Close();

            string urlW = null;

            if (UniformResourceLocatorWBytes[1] == 0)
                urlW = Encoding.Unicode.GetString(UniformResourceLocatorWBytes);
            else
                urlW = Encoding.ASCII.GetString(UniformResourceLocatorWBytes);

            return urlW;

        case TEXT_X_MOZ_URL:

            //use the underlying IDataObject to get the FileGroupDescriptorW as a MemoryStream
            MemoryStream textMozStream = (MemoryStream)this.underlyingDataObject.GetData(TEXT_X_MOZ_URL);
            byte[] textMozBytes = new byte[textMozStream.Length];
            textMozStream.Read(textMozBytes, 0, textMozBytes.Length);
            textMozStream.Close();

            string urlText = null;

            if (textMozBytes[1] == 0)
                urlText = Encoding.Unicode.GetString(textMozBytes);
            else
                urlText = Encoding.ASCII.GetString(textMozBytes);

            return urlText;

        case "text/html":

            //use the underlying IDataObject to get the FileGroupDescriptorW as a MemoryStream
            MemoryStream dataFormatStream = (MemoryStream)this.underlyingDataObject.GetData("text/html");
            byte[] dataFormatBytes = new byte[dataFormatStream.Length];
            dataFormatStream.Read(dataFormatBytes, 0, dataFormatBytes.Length);
            dataFormatStream.Close();

            string formatText = null;

            if (dataFormatBytes[1] == 0)
                formatText = Encoding.Unicode.GetString(dataFormatBytes);
            else
                formatText = Encoding.ASCII.GetString(dataFormatBytes);

            return formatText;
    }

    //use underlying IDataObject to handle getting of data
    return this.underlyingDataObject.GetData(format, autoConvert);
}

I can get quite a bit of data out of it, but just not in the same was as if it was dropped on the desktop:

private void Form1_DragDrop(object sender, DragEventArgs e)
{
    string[] dataFormats = e.Data.GetFormats();

    Dictionary<string, object> dataDictionary = new Dictionary<string, object>();

    foreach (string dataFormat in dataFormats)
    {
        dataDictionary.Add(dataFormat, e.Data.GetData(dataFormat));

        Debug.WriteLine(
            String.Format("Data Format: {0}     Has data: {1}       Data: {2}",
                dataFormat,
                dataDictionary[dataFormat] != null ? "Yes" : "No",
                dataDictionary[dataFormat] != null ? dataDictionary[dataFormat] : string.Empty));
    }

    DataObjectWrapper dataWrapper = new DragDropTest.DataObjectWrapper(e.Data);

    DataObjectWrapper.NativeMethods.FILEGROUPDESCRIPTORW fileGroupDescriptorW = dataWrapper.GetFileGroupDescriptorW();

    string url = dataWrapper.GetUniformResourceLocatorA();
    string urlw = dataWrapper.GetUniformResourceLocatorW();
    string mozUrl = dataWrapper.GetTextMozURL();
    string textHTML = dataWrapper.GetTExtHtml();

    Stream[] streams = dataWrapper.GetFileContents();

    byte[] buffer = new byte[1024];

    int received = 0;

    FileStream fileStream = File.OpenWrite(@"c:\temp\hello.txt");
    using (Stream input = streams[0])
    {
        int size = input.Read(buffer, 0, buffer.Length);
        while (size > 0)
        {
            fileStream.Write(buffer, 0, size);
            received += size;

            size = input.Read(buffer, 0, buffer.Length);
        }
    }

    fileStream.Flush();
    fileStream.Close();

    return;
}

I have read and tried the below links already:

http://dlaa.me/blog/post/9913083

Drag and Drop large virtual files with IStream using VirtualFileDataObject

Drag and drop large virtual files from C# to Windows Explorer

https://msdn.microsoft.com/en-us/library/windows/desktop/bb776902(v=vs.85).aspx#CFSTR_FILECONTENTS

https://msdn.microsoft.com/en-us/library/windows/desktop/bb776904(v=vs.85).aspx

https://dlaa.me/blog/post/9923072

Drag and drop virtual files using IStream

https://blogs.msdn.microsoft.com/adamroot/2008/02/19/shell-style-drag-and-drop-in-net-wpf-and-winforms/

Implementing drag-drop from Chrome on my .NET Windows form

https://blogs.msdn.microsoft.com/adamroot/2008/02/19/shell-style-drag-and-drop-in-net-part-3/

https://blogs.msdn.microsoft.com/adamroot/2008/02/19/shell-style-drag-and-drop-in-net-wpf-and-winforms/

https://blogs.msdn.microsoft.com/adamroot/2008/02/19/shell-style-drag-and-drop-in-net-part-2/

https://blogs.msdn.microsoft.com/adamroot/2008/02/02/dragdroplib-cs/

https://www.codeproject.com/reference/1091137/windows-clipboard-formats

http://dlaa.me/blog/post/9923072

Drag and drop virtual files using IStream

http://www.ookii.org/Blog/opening_files_via_idroptarget_in_net

https://www.codeproject.com/Articles/28209/Outlook-Drag-and-Drop-in-C

回答1:

You should be able to drag & drop embedded images with your code. But attachments like zip or pdf files are transfered as "FileDrop" format from Chrome. Unfortunately it uses the asynchronous drag & drop interface which is not natively supported by DotNet. That's why GetData("FileDrop") will always return null.

You need to use the interface IDataObjectAsyncCapability to trigger the download. But first it is necessary to pick apart the DataObject to get to the underlying COM-DataObject:

using System;
using System.Collections.Specialized;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Threading.Tasks;

namespace Dummy
{
    public class FileDrop
    {
        [ComImport]
        [Guid("3D8B0590-F691-11d2-8EA9-006097DF5BD4")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        public interface IDataObjectAsyncCapability
        {
            void SetAsyncMode([In] Int32 fDoOpAsync);
            void GetAsyncMode([Out] out Int32 pfIsOpAsync);
            void StartOperation([In] IBindCtx pbcReserved);
            void InOperation([Out] out Int32 pfInAsyncOp);
            void EndOperation([In] Int32 hResult, [In] IBindCtx pbcReserved, [In] UInt32 dwEffects);
        }

        public static async Task<StringCollection> GetFileDrop(System.Windows.Forms.DataObject dataobject)
        {
            if (dataobject.ContainsFileDropList())
            {
                var files = dataobject.GetFileDropList();
                if (files.Count == 0)
                {
                    // try async version
                    if (await ProcessFileDropAsync(dataobject))
                    {
                        files = dataobject.GetFileDropList();
                    }
                }

                return files;
            }

            // return empty collection    
            return new StringCollection();
        }

        private static async Task<bool> ProcessFileDropAsync(System.Windows.Forms.DataObject dataobject)
        {
            // get the internal ole dataobject
            FieldInfo innerDataField = dataobject.GetType().GetField("innerData", BindingFlags.NonPublic | BindingFlags.Instance);
            var oledataobject = (System.Windows.Forms.IDataObject)innerDataField.GetValue(dataobject);

            var type = oledataobject.GetType();
            if (type.Name == "OleConverter")
            {
                // get the COM-object from the OleConverter
                FieldInfo innerDataField2 = type.GetField("innerData", BindingFlags.NonPublic | BindingFlags.Instance);
                var item = innerDataField2.GetValue(oledataobject);

                var asyncitem = item as IDataObjectAsyncCapability;

                if (asyncitem != null)
                {
                    var isasync = 0;
                    asyncitem.GetAsyncMode(out isasync);
                    if (isasync != 0)
                    {
                        var task = Task.Run(() =>
                        {
                            asyncitem.StartOperation(null);

                            // calling GetData after StartOperation will trigger the download in Chrome
                            // subsequent calls to GetData return cached data
                            // files are downloaded to something like c:\temp\chrome_drag1234_123456789\yourfilename.here
                            var result = dataobject.GetData("FileDrop"); 

                            asyncitem.EndOperation(0, null, 1); // DROPEFFECT_COPY = 1                    
                        });

                        await task;

                        return true;
                    }                    
                }
            }

            return false;
        }
    }
}

Call it from your drop handler and files should contain the path to your attachment:

    private async void panel_DragDrop(object sender, DragEventArgs e)
    {
        var files = await FileDrop.GetFileDrop(e.Data as System.Windows.Forms.DataObject);

        foreach (var file in files)
        {
            Console.WriteLine(file);
        }
    }