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