我如何像Windows资源管理器一样从chrome检索拖放数据 [英] How can I retrieve the drag-drop data from chrome, like Windows Explorer does
问题描述
如果我将图像从Chrome(或Firefox)拖到桌面上,则Windows资源管理器(不是IE)会保存文件.
If I drag an image from Chrome (or Firefox) onto the Desktop, Windows Explorer (not IE) is able to save the file.
但是,如果我尝试从 System.Windows.Forms.IDataObject
获取位图,则没有可用于Chrome的位图数据(只有Firefox的DIB).
However, if I attempt to get the Bitmap from the System.Windows.Forms.IDataObject
, there is no Bitmap data available for Chrome (and only DIB from Firefox).
这让我感到困惑,当 IDataObject
中没有可用的资源时,Explorer如何从Chrome/Firefox获取实际图像?Microsoft是否具有唯一的功能,只有Windows才能使用它来提取图像数据?我知道如何通过URI或FileDrop临时文件(参见下文)获取图像.问题是如何获取提供的文件",就像资源管理器一样.
So riddle me this, how is Explorer able to get the actual image from Chrome/Firefox, when it's not available in IDataObject
? Does Microsoft have a undocumented feature the only Windows can use to extract the image data? I know how to get the image via the URI, or the FileDrop tempfile (see below). The question is "how to get the served file", like Explorer does.
一些注意事项:
- 放置在桌面上的JPEG与提供的JPEG相同(md5sum),并且保留了EXIF数据,因此无法将DIB(如果存在)转换为JPEG.
- 图片位于经过身份验证的会话之后,因此资源管理器无法通过URL提取文件.
- 两个浏览器都提供
DragImageBits
,但这不是原始图像(太小),而且仅应将其用作拖动时的预览(Explorer会这样做). - Firefox提供了一个
FileDrop
数组,其中包含一个临时文件,但这是BMP,而不是JPEG.
- The JPEG dropped onto the Desktop is identical (md5sum) to the one served, plus EXIF data is preserved, so it can't be converting the DIB (if present) to a JPEG.
- The Image is behind an authenticated session, so Explorer can't be fetching the file by the URL.
- Both browsers provide
DragImageBits
, but this isn't the original image (it's too small), plus it's only supposed to be used as an preview when dragging (Explorer does this). - Firefox provides a
FileDrop
array which contains a tempfile, but this is a BMP, not a JPEG.
我实际上写了一个小应用程序来显示Paste/DragDrop事件中的 IDataObject
数据,以解决此问题,但无济于事.
I actually wrote a small app to display IDataObject
data from Paste/DragDrop events in order to solve this, but to no avail.
来源&在 GitHub
推荐答案
在阅读了有关 System.Windows.Forms.IDataObject
类的一些注释后,这些注释未公开 IStream
(我一无所知),我遇到了CodeProject文章 Outlook在C#中的拖放.
After reading some comments about the System.Windows.Forms.IDataObject
class not exposing IStream
(which I had no idea about), I came across a CodeProject article Outlook Drag and Drop in C#.
邮件消息是一个问题,因为OS调用返回一个IStorage,它是一种复合文件类型,并且 IDataObject的C#实现再次使我们失望,因为它不处理这种类型的返回,因此您得到的是null.
因此,从该示例中大胆地提出(我没有实现 IStorage
,因为我主要感兴趣的是 HGlobal
),我创建了一组扩展方法,使我可以检索FileContent.
So, lifting liberally from that example (I haven't implement IStorage
, as what I was mainly interested about turned out to be HGlobal
) I created a set of extension methods that allow me to retrieve the FileContent.
用法:
var fileNames = e.Data.GetFileContentNames();
for (int i = 0; i < files.Length; i++) {
using (var ms = e.Data.GetFileContent(i)) {
// Do something with your unadulterated content!
}
}
代码:
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Windows.Forms;
namespace DragDropViewer.ExtensionMethods {
/// <summary>Helper methods for getting FileContents from DragDrop data.</summary>
/// <see cref="!:https://www.codeproject.com/Articles/28209/Outlook-Drag-and-Drop-in-C"/>
public static class IDataObjectExtensionMethods {
/// <summary>Gets the array of FileNames from the FileGroupDescriptors format.</summary>
public static string[] GetFileContentNames(this System.Windows.Forms.IDataObject data) {
var names = new string[data.GetFileContentCount()];
if (names.Length != 0) {
var bytes = data.GetFileGroupDescriptor().ToArray();
IntPtr fgdPtr = IntPtr.Zero;
try {
fgdPtr = Marshal.AllocHGlobal(bytes.Length);
int offset = Marshal.SizeOf(typeof(UInt32));
int size = Marshal.SizeOf(typeof(FILEDESCRIPTORW));
for (int i = 0; i < names.Length; i++) {
var fd = (FILEDESCRIPTORW)Marshal.PtrToStructure(fgdPtr + offset + (i * size), typeof(FILEDESCRIPTORW));
names[i] = fd.cFileName;
}
} finally {
if (fgdPtr != IntPtr.Zero) Marshal.FreeHGlobal(fgdPtr);
}
}
return names;
}
/// <summary>Gets the number of files available in the FileGroupDescriptor format.</summary>
public static int GetFileContentCount(this System.Windows.Forms.IDataObject data) {
// File count is stored as an UInt32 in the FileGroupDescriptor format
MemoryStream ms = data.GetFileGroupDescriptor();
if (ms == null) return 0;
using (var reader = new BinaryReader(ms)) {
return (int)reader.ReadUInt32(); // Assume this won't overflow!
}
}
/// <summary>Gets the file content for the specified FileDescriptor index.</summary>
/// <param name="index">The index of the file content to retrieve.</param>
public static MemoryStream GetFileContent(this System.Windows.Forms.IDataObject data, int index) {
// As this is indexed, "FileContent" is most likely null, so the COM IDataObject needs to be used
var comData = (System.Runtime.InteropServices.ComTypes.IDataObject)data;
var formatetc = new FORMATETC() {
cfFormat = (short)DataFormats.GetFormat("FileContents").Id,
dwAspect = DVASPECT.DVASPECT_CONTENT,
lindex = index,
ptd = IntPtr.Zero,
tymed = TYMED.TYMED_ISTREAM | TYMED.TYMED_HGLOBAL
};
var medium = new STGMEDIUM();
comData.GetData(ref formatetc, out medium);
switch (medium.tymed) {
case TYMED.TYMED_HGLOBAL:
return data.GetFileContentFromHGlobal(medium);
case TYMED.TYMED_ISTREAM:
return data.GetFileContentFromIStream(medium);
default:
throw new InvalidOperationException($"Cannot get FileContent for {medium.tymed} TYMED.");
}
}
private static MemoryStream GetFileContentFromHGlobal(this System.Windows.Forms.IDataObject data, STGMEDIUM medium) {
var innerDataField = data.GetType().GetField("innerData", BindingFlags.NonPublic | BindingFlags.Instance);
var oldData = (System.Windows.Forms.IDataObject)innerDataField.GetValue(data);
var getDataFromHGLOBLALMethod = oldData.GetType().GetMethod("GetDataFromHGLOBLAL", BindingFlags.NonPublic | BindingFlags.Instance);
return (MemoryStream)getDataFromHGLOBLALMethod.Invoke(oldData, new object[] { "FileContents", medium.unionmember });
}
private static MemoryStream GetFileContentFromIStream(this System.Windows.Forms.IDataObject data, STGMEDIUM medium) {
var iStream = (IStream)Marshal.GetObjectForIUnknown(medium.unionmember);
Marshal.Release(medium.unionmember);
var iStreamStat = new System.Runtime.InteropServices.ComTypes.STATSTG();
iStream.Stat(out iStreamStat, 0);
var content = new byte[(int)iStreamStat.cbSize];
iStream.Read(content, content.Length, IntPtr.Zero);
return new MemoryStream(content);
}
private static MemoryStream GetFileGroupDescriptor(this System.Windows.Forms.IDataObject data) {
MemoryStream ms = null;
if (data.GetDataPresent("FileGroupDescriptorW")) {
ms = (MemoryStream)data.GetData("FileGroupDescriptorW", true);
}
return ms;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct FILEDESCRIPTORW {
public UInt32 dwFlags;
public Guid clsid;
public System.Drawing.Size sizel;
public System.Drawing.Point pointl;
public UInt32 dwFileAttributes;
public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
public UInt32 nFileSizeHigh;
public UInt32 nFileSizeLow;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public String cFileName;
}
}
}
这篇关于我如何像Windows资源管理器一样从chrome检索拖放数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!