Skip to content

Commit

Permalink
Add support for IStream & HGlobal drag drops
Browse files Browse the repository at this point in the history
  • Loading branch information
john.bayly@tipstrade.net committed Apr 9, 2018
1 parent 1371551 commit bbdee7d
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 9 deletions.
3 changes: 3 additions & 0 deletions DragDropViewer/DragDropViewer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<NoWarn>IDE1006</NoWarn>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
Expand All @@ -29,6 +30,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<NoWarn>IDE1006</NoWarn>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
Expand All @@ -44,6 +46,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ExtensionMethods\ExtensionMethods.cs" />
<Compile Include="MainForm.cs">
<SubType>Form</SubType>
</Compile>
Expand Down
127 changes: 127 additions & 0 deletions DragDropViewer/ExtensionMethods/ExtensionMethods.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
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;
}
}
}
49 changes: 42 additions & 7 deletions DragDropViewer/MainForm.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using DragDropViewer.ExtensionMethods;
using System;
using System.Drawing;
using System.IO;
using System.Text;
Expand Down Expand Up @@ -79,6 +80,33 @@ private void LoadData(IDataObject data) {
}
}

var files = data.GetFileContentNames();
if (files.Length != 0) {
for (int i = 0; i < files.Length; i++) {
object parsedData;
using (var ms = data.GetFileContent(i)) {
ms.Position = 0;
using (var fs = new FileStream("y:\\DragDrop\\foo.jpeg", FileMode.Open, FileAccess.Write)) {
ms.WriteTo(fs);
}
ms.Position = 0;

try {
using (var img = new Bitmap(ms)) {
parsedData = new Bitmap(img);
}
} catch {
parsedData = ms.ToArray();
}
}
var item = new DataFormat() {
Data = parsedData,
Name = $"X-FileContent-{files[i]}"
};
listFormats.Items.Add(item);
}
}

listFormats.EndUpdate();
listFormats.SelectedIndex = -1;
listFormats.SelectedIndexChanged += listFormats_SelectedIndexChanged;
Expand Down Expand Up @@ -120,14 +148,19 @@ private void OnSelectedItemChanged() {
Lines = (string[])selected.Data
};

} else if (selected.Type == typeof(MemoryStream)) {
var ms = selected.Data as MemoryStream;
ms.Position = 0;
} else if ((selected.Type == typeof(MemoryStream)) || (selected.Type == typeof(byte[]))) {
byte[] bytes;

if (selected.Type == typeof(MemoryStream)) {
bytes = ((MemoryStream)selected.Data).ToArray();
} else {
bytes = (byte[])selected.Data;
}

string text;
if (DisplayHex) {
var sb = new StringBuilder();
foreach (byte b in ms.ToArray()) {
foreach (byte b in bytes) {
if (sb.Length != 0) sb.Append(" ");
sb.Append(b.ToString("x2"));
}
Expand All @@ -144,8 +177,10 @@ private void OnSelectedItemChanged() {
enc = Encoding.UTF8;
}

using (var reader = new StreamReader(ms, enc, true, 1024, true)) {
text = reader.ReadToEnd();
using (var ms = new MemoryStream(bytes)) {
using (var reader = new StreamReader(ms, enc, true, 1024, true)) {
text = reader.ReadToEnd();
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions DragDropViewer/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.1.0")]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: AssemblyVersion("1.1.0.0")]
[assembly: AssemblyFileVersion("1.1.0.0")]

0 comments on commit bbdee7d

Please sign in to comment.