|
Greetings Robert,
First off, great article!
I have a compression utility, where users can drag items from the archive (ListView) to the Windows file system. (Like WinZip..etc)
As in your situation, the files should only be extracted when needed. The problem is, I can't seem to get the drop target when the user releases the files, as my intention was to perform the extraction on the MouseUp event. Thus, my eternal searching has led me to your artical.
Would I be correct in assuming that I need to modify the private MemoryStream GetFileContents() method? For instance, I see your using the BinaryReader 'File.OpenRead()' object to pass/write the data to the MemorySream object. In my case, I think I would have to perform some sort of archival extraction, returning a similar object. Also, would there be a noticable delay creating FileContents for a considerably large file?
Cheers,
Greg
modified on Wednesday, April 9, 2008 7:14 PM
|
|
|
|
|
I just encountered that the solution is not working with every application that usually supports drag and drop from let's say Windows Explorer. At least in Lotus Notes you can open a new memo and drag and drop files from the Explorer to attach them to the email but it doesn't seem to work with the solution you described. Lotus Notes will display a "plus" sign indicating that drag and drop should work but when releasing the mouse nothing happens. In the code GetData is called with formats FileGroupDescriptorW and FileContents so everything looks fine there, no errors even when running in Debugger.
Have you ever noticed something similar, maybe with other applications?
|
|
|
|
|
First, I have to thank you for getting me closer to my goal than any article I've yet found. Now for the question (you knew it was coming, right?) The class you've generously provided works well for smallish files--up to about 12MB based on my admittedly limited testing. Unfortunately, if I try to drag and drop a larger virtual file (the one I tried was about 64MB) the system chokes and can't allocate the memory to create the HGLOBAL. From what I can gather, in instances like this you're supposed to use TYMED_ISTREAM, but I'm not quite sure how to fill the medium's unionmember when using IStreams. Any ideas?
|
|
|
|
|
Thanks for the comments. I tested on my end using a 64MB virtual file and it gives up for me too. I did some debugging and I see that when Windows Explorer is ready to accept the actual data it passes a TYMED of "TYMED_HGLOBAL | TYMED_ISTREAM | TYMED_ISTORAGE". My routine, based on the .NET Framework implementation of IDataObject.GetData favors HGLOBAL transfers over ISTREAM through its statement:
if ((formatetc.tymed & TYMED.TYMED_HGLOBAL) != TYMED.TYMED_NULL)
As a test, I came up with the following change to my code, checking for ISTREAM requests before HGLOBAL requests:
if (GetTymedUseable(formatetc.tymed))
{
if ((formatetc.tymed & TYMED.TYMED_ISTREAM) != TYMED.TYMED_NULL)
{
medium.tymed = TYMED.TYMED_ISTREAM;
((IComDataObject)this).GetDataHere(ref formatetc, ref medium);
}
else if ((formatetc.tymed & TYMED.TYMED_HGLOBAL) != TYMED.TYMED_NULL)
This did not solve the problem but appears to have shifted the problem to the file contents memory stream generation. I will have to experiment with a different transfer medium, perhaps going to an intermediate disk file.
In the meantime, hopefully there is a spark in the code above to give you a gem of inspiration!
|
|
|
|
|
That's about as far as I had gotten--apparantly, from what I can gather, after that point you need to set medium.unionmember to a pointer to the wrapped IStream you intend to pass the data with. There's an implementation of a wrapped IStream that seems to fit the bill at: http://www.koders.com/csharp/fid23BB694F4AFD5991BF05B935F90EB53D92087082.aspx?s=imageanimator[^]...but I'm still unsure of how to make that final linkage between the medium and the returned stream.
|
|
|
|
|
Well, I tried swapping the GlobalAlloc/GlobalFree out for VirtualAlloc/VirtualFree, using the IStream wrapper I linked earlier as the return type for GetFileContents()…the file descriptors are still being passed correctly, but I left those using GlobalAlloc/GlobalFree/MemoryStream, so that's not surprising. The file contents, however, don't seem to be making their way to the shell despite the VirtualAlloc returning what seems to be a valid memory address. Any idea how to attach any sort of debugger to this so I can at least see where things are going pearshaped? Or is this one of those "keeping trying different things until one of them works" situations?
The inserted snippet in GetData()
if (formatetc.cfFormat == (Int16)DataFormats.GetFormat(NativeMethods.CFSTR_FILECONTENTS).Id) {
if ((formatetc.tymed & TYMED.TYMED_ISTREAM) != TYMED.TYMED_NULL) {
medium.tymed = TYMED.TYMED_ISTREAM;
medium.unionmember = NativeMethods.VirtualAlloc(IntPtr.Zero, new UIntPtr(1), NativeMethods.MEM_COMMIT, NativeMethods.PAGE_READWRITE);
if (medium.unionmember == IntPtr.Zero) {
throw new OutOfMemoryException();
}
try {
((System.Runtime.InteropServices.ComTypes.IDataObject)this).GetDataHere(ref formatetc, ref medium);
return;
} catch {
NativeMethods.VirtualFree(medium.unionmember, new UIntPtr(1), NativeMethods.MEM_DECOMMIT);
medium.unionmember = IntPtr.Zero;
throw;
}
}
}
Any suggestions?
|
|
|
|
|
If you are using Visual Studio 2008, you can enable .NET Framework source code debugging and step into the GetDataHere() method. The instructions for configuring VS 2008 to access the source code can be found at [^]. Alternately, the source to the GetDataHere method could be extracted and added to the DataObjectEx class so that it can be debugged. When I get a chance, I'll try adding your code and see if I can step into the routines and see why it is failing.
|
|
|
|
|
Tried the debugging thing, but it didn't seem to recognize the recast call as an external call—it treated it as a symbol from my source, and didn't give any useful information that I could decode. It did, however, help me notice that I'd stupidly had the code checking for:
((formatetc.tymed & TYMED.TYMED_ISTREAM) != TYMED.TYMED_ISTREAM)
instead of what I should have been checking for:
((formatetc.tymed & TYMED.TYMED_ISTREAM) != TYMED.TYMED_NULL)
Unfortunately, that wasn't the problem. Back to the drawing board.
|
|
|
|
|
|
I would like to apologize to everyone who has emailed me over the months with questions. Due to employee resignations and layoffs my workload has been crazy and I have not had a chance to go back and revisit many of the issues brought up. The large files problem has been particularly tricky. Our quick "hack" until a resolution is found was to disallow the transfer of files beyond a certain size. This was not too bad for us because the limit is still quite high (several MB).
At this time, I am hoping to revisit the code in the new year. My firm is hoping for a few more sales by the end of the year and support for completing those has taken over a lot of my time. Heck, in this economy, I am glad to still have a job!
|
|
|
|
|
Nice to see that you have nit given up. Hope you will be able to solve it
|
|
|
|
|
Did someone have any luck with this issue ?
|
|
|
|
|
You have to properly implement the IStream interface (Google for IMemoryStream, for example), then before comparing to TYMED_HGLOBAL do
if (GetTymedUseable(formatetc.tymed))
{
if ((formatetc.tymed & TYMED.TYMED_ISTREAM) != TYMED.TYMED_NULL)
{
try
{
medium.tymed = TYMED.TYMED_ISTREAM;
IStream o = (IStream)GetData("FileContents", false);
medium.unionmember = Marshal.GetComInterfaceForObjectInContext(o, typeof(IStream));
return;
}
catch (Exception)
{
throw;
}
}
else
if ((formatetc.tymed & TYMED.TYMED_HGLOBAL) != TYMED.TYMED_NULL)
...
Easy... when you know
|
|
|
|
|
[DllImport("ole32.dll", PreserveSig = false)]
public static extern IStream CreateStreamOnHGlobal(IntPtr hGlobal, [MarshalAs(UnmanagedType.Bool)] bool fDeleteOnRelease);
must do it in
System.Runtime.InteropServices.ComTypes.IDataObject.SetData
not go to in .net objectdata
var iStream = NativeMethods.CreateStreamOnHGlobal(IntPtr.Zero, true);
use iStream.write to fill data
ptr = Marshal.GetComInterfaceForObject(iStream, typeof(IStream));
|
|
|
|
|
If you were able to shift the issue to the creating of the memory stream, could you just return a read only non-locking FileStream?
private FileStream GetFileContents(SelectedItem[] SelectedItems, Int32 FileNumber)
{
FileStream FileContentMemoryStream = null;
if (SelectedItems != null && FileNumber < SelectedItems.Length)
{
FileContentMemoryStream = new FileStream(SelectedItems[FileNumber].SourceFileName,FileMode.Open,FileAccess.Read);
}
return FileContentMemoryStream;
}
This could create an issue since it is not closed, but the fs could be made more global and cleaned up on the PERFORMEDDROPEFFECT.
Thanks,
Kevin
modified on Saturday, September 26, 2009 9:00 PM
|
|
|
|
|
I found one example on late-rendering on the web (don't know on which page) and was able to modify it that way, to support large-Files (I tested it with a 250 MB file) and IStream.
This example is written in for .NET 2.0 and far from being perfect but hopefully it will give some hints.
I will post the code of this two files
File 1 (Example.cs is 63 lines of code), File 2(VirtualFileDataObject.cs is 1125 lines of code)
// Example.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Delay;
using System.IO;
using System.Collections.Specialized;
using System.Runtime.InteropServices;
namespace DOExample
{
public partial class Example : Form
{
public Example()
{
InitializeComponent();
}
private void button1_MouseDown(object sender, MouseEventArgs e)
{
VirtualFileDataObject vfdo = new VirtualFileDataObject();
vfdo.IsAsynchronous = true;
vfdo.PreferredDropEffect = DragDropEffects.Copy;
VirtualFileDataObject.FileDescriptor fileDecriptor1 = new VirtualFileDataObject.FileDescriptor();
List<VirtualFileDataObject.FileDescriptor> fileDescriptors = new List<VirtualFileDataObject.FileDescriptor>();
string filePath = @"C:\Program Files (x86)\VMware\VMware Workstation\pkg\Tools.cab";
FileInfo fi = new FileInfo(filePath);
fileDecriptor1.Name = fi.Name;
fileDecriptor1.Length = fi.Length;
fileDecriptor1.ChangeTimeUtc = fi.LastWriteTimeUtc;
fileDecriptor1.ExtraInfo = fi.FullName;
fileDecriptor1.StreamContents = new VirtualFileDataObject.MyAction<Delay.VirtualFileDataObject.Tuple<Stream,string>>(GetFileData);
fileDescriptors.Add(fileDecriptor1);
vfdo.SetData(fileDescriptors);
vfdo.PreferredDropEffect = DragDropEffects.Copy;
button1.DoDragDrop(vfdo, DragDropEffects.Copy);
}
public void GetFileData(Delay.VirtualFileDataObject.Tuple<Stream, string> tuple)
{
using (FileStream sourceStream = new FileStream(tuple.Item2, FileMode.Open, FileAccess.Read))
{
byte[] buffer = new byte[1024 * 1024];
while (true)
{
int read = sourceStream.Read(buffer, 0, buffer.Length);
if (read <= 0)
break;
tuple.Item1.Write(buffer, 0, read);
}
}
}
}
}
------------------------------------------------------------------------------------------
// VirtualFileDataObject.cs
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Windows.Forms;
using System.Collections.Specialized;
namespace Delay
{
public class VirtualFileDataObject : System.Runtime.InteropServices.ComTypes.IDataObject, IAsyncOperation
{
public bool IsAsynchronous { get; set; }
private static short FILECONTENTS = (short)(DataFormats.GetFormat(NativeMethods.CFSTR_FILECONTENTS).Id);
private static short FILEDESCRIPTORW = (short)(DataFormats.GetFormat(NativeMethods.CFSTR_FILEDESCRIPTORW).Id);
private static short PASTESUCCEEDED = (short)(DataFormats.GetFormat(NativeMethods.CFSTR_PASTESUCCEEDED).Id);
private static short PERFORMEDDROPEFFECT = (short)(DataFormats.GetFormat(NativeMethods.CFSTR_PERFORMEDDROPEFFECT).Id);
private static short PREFERREDDROPEFFECT = (short)(DataFormats.GetFormat(NativeMethods.CFSTR_PREFERREDDROPEFFECT).Id);
private List<DataObject> _dataObjects = new List<DataObject>();
private bool _inOperation;
private Action<VirtualFileDataObject> _startAction;
private Action<VirtualFileDataObject> _endAction;
public VirtualFileDataObject()
{
IsAsynchronous = true;
}
public VirtualFileDataObject(Action<VirtualFileDataObject> startAction, Action<VirtualFileDataObject> endAction)
: this()
{
_startAction = startAction;
_endAction = endAction;
}
#region IDataObject Members
[SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Method doesn't decrease security.")]
int System.Runtime.InteropServices.ComTypes.IDataObject.DAdvise(ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection)
{
Marshal.ThrowExceptionForHR(NativeMethods.OLE_E_ADVISENOTSUPPORTED);
throw new NotImplementedException();
}
[SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Method doesn't decrease security.")]
void System.Runtime.InteropServices.ComTypes.IDataObject.DUnadvise(int connection)
{
Marshal.ThrowExceptionForHR(NativeMethods.OLE_E_ADVISENOTSUPPORTED);
throw new NotImplementedException();
}
[SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Method doesn't decrease security.")]
int System.Runtime.InteropServices.ComTypes.IDataObject.EnumDAdvise(out IEnumSTATDATA enumAdvise)
{
Marshal.ThrowExceptionForHR(NativeMethods.OLE_E_ADVISENOTSUPPORTED);
throw new NotImplementedException();
}
[SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Method doesn't decrease security.")]
IEnumFORMATETC System.Runtime.InteropServices.ComTypes.IDataObject.EnumFormatEtc(DATADIR direction)
{
if (direction == DATADIR.DATADIR_GET)
{
if (0 == _dataObjects.Count)
{
throw new InvalidOperationException("VirtualFileDataObject requires at least one data object to enumerate.");
}
IEnumFORMATETC enumerator;
List<FORMATETC> formatetcs = new List<FORMATETC>(_dataObjects.Count);
foreach (DataObject d in _dataObjects)
{
formatetcs.Add(d.FORMATETC);
}
if (NativeMethods.SUCCEEDED(NativeMethods.SHCreateStdEnumFmtEtc((uint)(_dataObjects.Count), formatetcs.ToArray(), out enumerator)))
{
return enumerator;
}
Marshal.ThrowExceptionForHR(NativeMethods.E_FAIL);
}
throw new NotImplementedException();
}
int System.Runtime.InteropServices.ComTypes.IDataObject.GetCanonicalFormatEtc(ref FORMATETC formatIn, out FORMATETC formatOut)
{
throw new NotImplementedException();
}
[SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Method doesn't decrease security.")]
void System.Runtime.InteropServices.ComTypes.IDataObject.GetData(ref FORMATETC format, out STGMEDIUM medium)
{
medium = new STGMEDIUM();
var hr = ((System.Runtime.InteropServices.ComTypes.IDataObject)this).QueryGetData(ref format);
if (NativeMethods.SUCCEEDED(hr))
{
DataObject dataObject = null;
foreach (DataObject d in _dataObjects)
{
if ((d.FORMATETC.cfFormat == format.cfFormat) &&
(d.FORMATETC.dwAspect == format.dwAspect) &&
(0 != (d.FORMATETC.tymed & format.tymed) &&
(d.FORMATETC.lindex == format.lindex)))
{
dataObject = d;
break;
}
}
if (dataObject != null)
{
if (!IsAsynchronous && (FILEDESCRIPTORW == dataObject.FORMATETC.cfFormat) && !_inOperation)
{
_inOperation = true;
if (null != _startAction)
{
_startAction(this);
}
}
medium.tymed = dataObject.FORMATETC.tymed;
var result = dataObject.GetData();
hr = result.Item2;
if (NativeMethods.SUCCEEDED(hr))
{
medium.unionmember = result.Item1;
}
}
else
{
hr = NativeMethods.DV_E_FORMATETC;
}
}
if (!NativeMethods.SUCCEEDED(hr))
{
Marshal.ThrowExceptionForHR(hr);
}
}
void System.Runtime.InteropServices.ComTypes.IDataObject.GetDataHere(ref FORMATETC format, ref STGMEDIUM medium)
{
throw new NotImplementedException();
}
int System.Runtime.InteropServices.ComTypes.IDataObject.QueryGetData(ref FORMATETC format)
{
List<DataObject> formatMatches = new List<DataObject>();
foreach (DataObject d in _dataObjects)
{
if (d.FORMATETC.cfFormat == format.cfFormat)
{
formatMatches.Add(d);
}
}
if (formatMatches.Count == 0)
{
return NativeMethods.DV_E_FORMATETC;
}
List<DataObject> tymedMatches = new List<DataObject>();
foreach (DataObject d in formatMatches)
{
if ((d.FORMATETC.tymed & format.tymed) != 0)
{
tymedMatches.Add(d);
}
}
if (tymedMatches.Count == 0)
{
return NativeMethods.DV_E_TYMED;
}
List<DataObject> aspectMatches = new List<DataObject>();
foreach (DataObject d in tymedMatches)
{
if (d.FORMATETC.dwAspect == format.dwAspect)
{
aspectMatches.Add(d);
}
}
if (aspectMatches.Count == 0)
{
return NativeMethods.DV_E_DVASPECT;
}
return NativeMethods.S_OK;
}
[SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Method doesn't decrease security.")]
void System.Runtime.InteropServices.ComTypes.IDataObject.SetData(ref FORMATETC formatIn, ref STGMEDIUM medium, bool release)
{
var handled = false;
if ((formatIn.dwAspect == DVASPECT.DVASPECT_CONTENT) &&
(formatIn.tymed == TYMED.TYMED_HGLOBAL) &&
(medium.tymed == formatIn.tymed))
{
var ptr = NativeMethods.GlobalLock(medium.unionmember);
if (IntPtr.Zero != ptr)
{
try
{
var length = NativeMethods.GlobalSize(ptr).ToInt32();
byte[] data = new byte[length];
Marshal.Copy(ptr, data, 0, length);
SetData(formatIn.cfFormat, data);
handled = true;
}
finally
{
NativeMethods.GlobalUnlock(medium.unionmember);
}
}
if (release)
{
Marshal.FreeHGlobal(medium.unionmember);
}
}
if (!IsAsynchronous && (PERFORMEDDROPEFFECT == formatIn.cfFormat) && _inOperation)
{
if (null != _endAction)
{
_endAction(this);
}
_inOperation = false;
}
if (!handled)
{
throw new NotImplementedException();
}
}
#endregion
public void SetData(StringCollection fileDropList)
{
DataObject dataObj = new DataObject();
}
public void SetData(short dataFormat, byte[] data)
{
_dataObjects.Add(
new DataObject
{
FORMATETC = new FORMATETC
{
cfFormat = dataFormat,
ptd = IntPtr.Zero,
dwAspect = DVASPECT.DVASPECT_CONTENT,
lindex = -1,
tymed = TYMED.TYMED_HGLOBAL
},
GetData = () =>
{
var dataArray = data;
var ptr = Marshal.AllocHGlobal(dataArray.Length);
Marshal.Copy(dataArray, 0, ptr, dataArray.Length);
return new Tuple<IntPtr, int>(ptr, NativeMethods.S_OK);
},
});
}
public void SetData(short dataFormat, int index, MyAction<Tuple<Stream,string>> streamAndId, FileDescriptor fileDescriptor)
{
_dataObjects.Add(
new DataObject
{
FORMATETC = new FORMATETC
{
cfFormat = dataFormat,
ptd = IntPtr.Zero,
dwAspect = DVASPECT.DVASPECT_CONTENT,
lindex = index,
tymed = TYMED.TYMED_ISTREAM
},
GetData = () =>
{
var ptr = IntPtr.Zero;
var iStream = NativeMethods.CreateStreamOnHGlobal(IntPtr.Zero, true);
if (streamAndId != null)
{
using (var stream = new IStreamWrapper(iStream))
{
streamAndId.Invoke(new Tuple<Stream,string>(stream, fileDescriptor.ExtraInfo));
}
}
ptr = Marshal.GetComInterfaceForObject(iStream, typeof(IStream));
Marshal.ReleaseComObject(iStream);
return new Tuple<IntPtr, int>(ptr, NativeMethods.S_OK);
},
});
}
public void SetData(List<FileDescriptor> fileDescriptors)
{
var bytes = new List<byte>();
bytes.AddRange(StructureBytes(new NativeMethods.FILEGROUPDESCRIPTOR { cItems = (uint)(fileDescriptors.Count) }));
foreach (var fileDescriptor in fileDescriptors)
{
var FILEDESCRIPTOR = new NativeMethods.FILEDESCRIPTOR
{
cFileName = fileDescriptor.Name,
};
if (fileDescriptor.ChangeTimeUtc.HasValue)
{
FILEDESCRIPTOR.dwFlags |= NativeMethods.FD_CREATETIME | NativeMethods.FD_WRITESTIME;
var changeTime = fileDescriptor.ChangeTimeUtc.Value.ToLocalTime().ToFileTime();
var changeTimeFileTime = new System.Runtime.InteropServices.ComTypes.FILETIME
{
dwLowDateTime = (int)(changeTime & 0xffffffff),
dwHighDateTime = (int)(changeTime >> 32),
};
FILEDESCRIPTOR.ftLastWriteTime = changeTimeFileTime;
FILEDESCRIPTOR.ftCreationTime = changeTimeFileTime;
}
if (fileDescriptor.Length.HasValue)
{
FILEDESCRIPTOR.dwFlags |= NativeMethods.FD_FILESIZE;
FILEDESCRIPTOR.nFileSizeLow = (uint)(fileDescriptor.Length & 0xffffffff);
FILEDESCRIPTOR.nFileSizeHigh = (uint)(fileDescriptor.Length >> 32);
}
bytes.AddRange(StructureBytes(FILEDESCRIPTOR));
}
SetData(FILEDESCRIPTORW, bytes.ToArray());
var index = 0;
foreach (var fileDescriptor in fileDescriptors)
{
SetData(FILECONTENTS, index, fileDescriptor.StreamContents, fileDescriptor);
index++;
}
}
public DragDropEffects? PasteSucceeded
{
get { return GetDropEffect(PASTESUCCEEDED); }
set
{
List<byte> data = new List<byte>(BitConverter.GetBytes((UInt32)value));
SetData(PASTESUCCEEDED, data.ToArray());
}
}
public DragDropEffects? PerformedDropEffect
{
get { return GetDropEffect(PERFORMEDDROPEFFECT); }
set
{
List<byte> data = new List<byte>(BitConverter.GetBytes((UInt32)value));
SetData(PERFORMEDDROPEFFECT, data.ToArray());
}
}
public DragDropEffects? PreferredDropEffect
{
get { return GetDropEffect(PREFERREDDROPEFFECT); }
set
{
List<byte> data = new List<byte>(BitConverter.GetBytes((UInt32)value));
SetData(PREFERREDDROPEFFECT, data.ToArray());
}
}
[SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Method doesn't decrease security.")]
private DragDropEffects? GetDropEffect(short format)
{
DataObject dataObject = null;
foreach (DataObject d in _dataObjects)
{
if ((format == d.FORMATETC.cfFormat) &&
(DVASPECT.DVASPECT_CONTENT == d.FORMATETC.dwAspect) &&
(TYMED.TYMED_HGLOBAL == d.FORMATETC.tymed))
{
dataObject = d;
}
}
if (null != dataObject)
{
var result = dataObject.GetData();
if (NativeMethods.SUCCEEDED(result.Item2))
{
var ptr = NativeMethods.GlobalLock(result.Item1);
if (IntPtr.Zero != ptr)
{
try
{
var length = NativeMethods.GlobalSize(ptr).ToInt32();
if (4 == length)
{
var data = new byte[length];
Marshal.Copy(ptr, data, 0, length);
return (DragDropEffects)(BitConverter.ToUInt32(data, 0));
}
}
finally
{
NativeMethods.GlobalUnlock(result.Item1);
}
}
}
}
return null;
}
#region IAsyncOperation Members
void IAsyncOperation.SetAsyncMode(int fDoOpAsync)
{
IsAsynchronous = !(NativeMethods.VARIANT_FALSE == fDoOpAsync);
}
void IAsyncOperation.GetAsyncMode(out int pfIsOpAsync)
{
pfIsOpAsync = IsAsynchronous ? NativeMethods.VARIANT_TRUE : NativeMethods.VARIANT_FALSE;
}
void IAsyncOperation.StartOperation(IBindCtx pbcReserved)
{
_inOperation = true;
if (null != _startAction)
{
_startAction(this);
}
}
void IAsyncOperation.InOperation(out int pfInAsyncOp)
{
pfInAsyncOp = _inOperation ? NativeMethods.VARIANT_TRUE : NativeMethods.VARIANT_FALSE;
}
void IAsyncOperation.EndOperation(int hResult, IBindCtx pbcReserved, uint dwEffects)
{
if (null != _endAction)
{
_endAction(this);
}
_inOperation = false;
}
#endregion
[SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Method doesn't decrease security.")]
private static IEnumerable<byte> StructureBytes(object source)
{
var size = Marshal.SizeOf(source.GetType());
var ptr = Marshal.AllocHGlobal(size);
var bytes = new byte[size];
try
{
Marshal.StructureToPtr(source, ptr, false);
Marshal.Copy(ptr, bytes, 0, size);
}
finally
{
Marshal.FreeHGlobal(ptr);
}
return bytes;
}
public delegate void MyAction<T>(T obj);
[SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Deliberate to provide obvious coupling.")]
public class FileDescriptor
{
public string Name { get; set; }
public Int64? Length { get; set; }
public DateTime? ChangeTimeUtc { get; set; }
public MyAction<Tuple<Stream, string>> StreamContents { get; set; }
public string ExtraInfo { get; set; }
}
private class DataObject
{
public FORMATETC FORMATETC { get; set; }
public Func<Tuple<IntPtr, int>> GetData { get; set; }
}
public class Tuple<T1, T2>
{
public T1 Item1 { get; private set; }
public T2 Item2 { get; private set; }
public Tuple(T1 item1, T2 item2)
{
Item1 = item1;
Item2 = item2;
}
}
public delegate TResult Func<TResult>();
private class IStreamWrapper : Stream
{
private IStream _iStream;
public IStreamWrapper(IStream iStream)
{
_iStream = iStream;
}
public override bool CanRead
{
get { return false; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return true; }
}
public override void Flush()
{
throw new NotImplementedException();
}
public override long Length
{
get { throw new NotImplementedException(); }
}
public override long Position
{
get { throw new NotImplementedException(); }
set { throw new NotImplementedException(); }
}
public override int Read(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
if (offset == 0)
{
_iStream.Write(buffer, count, IntPtr.Zero);
}
else
{
ArraySegment<byte> buf = new ArraySegment<byte>(buffer, offset, count);
_iStream.Write(buf.Array, count, IntPtr.Zero);
}
}
}
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "dragSource", Justification = "Parameter is present so the signature matches that of System.Windows.DragDrop.DoDragDrop.")]
public static DragDropEffects DoDragDrop(object dragSource, System.Runtime.InteropServices.ComTypes.IDataObject dataObject, DragDropEffects allowedEffects)
{
int[] finalEffect = new int[1];
try
{
NativeMethods.DoDragDrop(dataObject, new DropSource(), (int)allowedEffects, finalEffect);
}
finally
{
var virtualFileDataObject = dataObject as VirtualFileDataObject;
if ((null != virtualFileDataObject) && !virtualFileDataObject.IsAsynchronous && virtualFileDataObject._inOperation)
{
if (null != virtualFileDataObject._endAction)
{
virtualFileDataObject._endAction(virtualFileDataObject);
}
virtualFileDataObject._inOperation = false;
}
}
return (DragDropEffects)(finalEffect[0]);
}
private class DropSource : NativeMethods.IDropSource
{
public int QueryContinueDrag(int fEscapePressed, uint grfKeyState)
{
var escapePressed = (0 != fEscapePressed);
var keyStates = (DragDropKeyStates)grfKeyState;
if (escapePressed)
{
return NativeMethods.DRAGDROP_S_CANCEL;
}
else if (DragDropKeyStates.None == (keyStates & DragDropKeyStates.LeftMouseButton))
{
return NativeMethods.DRAGDROP_S_DROP;
}
return NativeMethods.S_OK;
}
public int GiveFeedback(uint dwEffect)
{
return NativeMethods.DRAGDROP_S_USEDEFAULTCURSORS;
}
}
public static class NativeMethods
{
public const int DRAGDROP_S_DROP = 0x00040100;
public const int DRAGDROP_S_CANCEL = 0x00040101;
public const int DRAGDROP_S_USEDEFAULTCURSORS = 0x00040102;
public const int DV_E_DVASPECT = -2147221397;
public const int DV_E_FORMATETC = -2147221404;
public const int DV_E_TYMED = -2147221399;
public const int E_FAIL = -2147467259;
public const uint FD_CREATETIME = 0x00000008;
public const uint FD_WRITESTIME = 0x00000020;
public const uint FD_FILESIZE = 0x00000040;
public const int OLE_E_ADVISENOTSUPPORTED = -2147221501;
public const int S_OK = 0;
public const int S_FALSE = 1;
public const int VARIANT_FALSE = 0;
public const int VARIANT_TRUE = -1;
public const string CFSTR_FILECONTENTS = "FileContents";
public const string CFSTR_FILEDESCRIPTORW = "FileGroupDescriptorW";
public const string CFSTR_PASTESUCCEEDED = "Paste Succeeded";
public const string CFSTR_PERFORMEDDROPEFFECT = "Performed DropEffect";
public const string CFSTR_PREFERREDDROPEFFECT = "Preferred DropEffect";
[SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "Structure exists for interop.")]
[StructLayout(LayoutKind.Sequential)]
public struct FILEGROUPDESCRIPTOR
{
public UInt32 cItems;
}
[SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "Structure exists for interop.")]
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct FILEDESCRIPTOR
{
public UInt32 dwFlags;
public Guid clsid;
public Int32 sizelcx;
public Int32 sizelcy;
public Int32 pointlx;
public Int32 pointly;
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;
}
[SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "Structure exists for interop.")]
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct POINT
{
public Int32 X;
public Int32 Y;
public POINT(int x, int y)
{
this.X = x;
this.Y = y;
}
}
[SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "Structure exists for interop.")]
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct DROPFILES
{
public int pFiles;
public POINT point;
public bool fNC;
public bool fWide;
}
[ComImport]
[Guid("00000121-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IDropSource
{
[PreserveSig]
int QueryContinueDrag(int fEscapePressed, uint grfKeyState);
[PreserveSig]
int GiveFeedback(uint dwEffect);
}
[SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "Win32 API.")]
[DllImport("shell32.dll")]
public static extern int SHCreateStdEnumFmtEtc(uint cfmt, FORMATETC[] afmt, out IEnumFORMATETC ppenumFormatEtc);
[return: MarshalAs(UnmanagedType.Interface)]
[DllImport("ole32.dll", PreserveSig = false)]
public static extern IStream CreateStreamOnHGlobal(IntPtr hGlobal, [MarshalAs(UnmanagedType.Bool)] bool fDeleteOnRelease);
[DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true, PreserveSig = false)]
public static extern void DoDragDrop(System.Runtime.InteropServices.ComTypes.IDataObject dataObject, IDropSource dropSource, int allowedEffects, int[] finalEffect);
[DllImport("kernel32.dll")]
public static extern IntPtr GlobalLock(IntPtr hMem);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("kernel32.dll")]
public static extern bool GlobalUnlock(IntPtr hMem);
[DllImport("kernel32.dll")]
public static extern IntPtr GlobalSize(IntPtr handle);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true)]
public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam,
IntPtr lParam);
public const uint WM_DROPFILES = 0x0233;
public static bool SUCCEEDED(int hr)
{
return (0 <= hr);
}
}
}
[ComImport]
[Guid("3D8B0590-F691-11d2-8EA9-006097DF5BD4")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IAsyncOperation
{
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);
}
[Flags]
public enum DragDropKeyStates
{
None = 0,
LeftMouseButton = 1,
RightMouseButton = 2,
ShiftKey = 4,
ControlKey = 8,
MiddleMouseButton = 16,
AltKey = 32,
}
}
|
|
|
|
|
This is really SWEET!
My question is, how do I get my virtual files into a folder(virtual or otherwise) without pasting them from the clipboard?
In other words, I want to seperate the visual portion of the virtual files from the actual files themselves.
|
|
|
|
|
The only method I can think of off the top of my head that allows the transfer without using the clipboard is through a drag/drop operation. The following code snippet shows and example:
DataObjectEx.SelectedItem[] SelectedItems =
new DataObjectEx.SelectedItem[MyListView.SelectedItems.Count];
//TODO: Populate SelectedItems array
DataObjectEx dataObject = new DataObjectEx(SelectedItems);
dataObject.SetData(NativeMethods.CFSTR_FILEDESCRIPTORW, null);
dataObject.SetData(NativeMethods.CFSTR_FILECONTENTS, null);
dataObject.SetData(NativeMethods.CFSTR_PERFORMEDDROPEFFECT, null);
MyListView.DoDragDrop(dataObject, DragDropEffects.Move |
DragDropEffects.Copy);
|
|
|
|
|
That looks like it should work, but where do you set the destination directory?
|
|
|
|
|
The destination directory would have to be set in the drop target application. When using Explorer as the drop target, the folder you are dropping to is the directory the files will be dropped to. If you are using a custom application as the drop target, it will have to implement the IDropTarget interface either natively or through the DragDrop event in .NET. That application will have to decide where to create the dropped files. It would parse the CFSTR_FILEDESCRIPTOR data to retrieve the filenames, create the files in the appropriate directories and then request the CFSTR_FILECONTENTS with the appropriate lindex value and write those contents to the files.
I have not yet implemented the reception of CFSTR_FILEDESCRIPTOR or CFSTR_FILECONTENTS so I have no code snippets to post here.
|
|
|
|
|
I understand the process, as I've done it under C++, but not C#.
I suspect the process is EXACTLY the same, however the actual code will be different.
I think you've answered my question to my satisfaction. I see the way to a solution, I don't expect you to do all of my work for me(lol).
Thanks!
|
|
|
|
|
|
Hi Robert, this is exactly what I've been looking for! At least I hope so! Unfortunately I cannot get it working. It throws tons of errors like this
{"Invalid FORMATETC structure (Exception from HRESULT: 0x80040064 (DV_E_FORMATETC))"}
when calling:
((System.Runtime.InteropServices.ComTypes.IDataObject)this).GetDataHere(ref formatetc, ref medium);
I already found a hint in some newsgroup to deselect the option "Break when exceptions cross AppDomain" within VS.Net but it doesn't help and I think it'd just cover what's really going wrong.
Maybe it'll help if you could provide a working sample that - just for demo purposes - uses "real" files from your local filesystem?!
Regards, Björn
|
|
|
|
|
I have created a simple working demo package that I have merged into the download file. It is a ZIP file called DataObjectExample.zip. It grabs three files from C:\Windows\System32, "virtualizes" them and puts them on the clipboard. It also turned up the fact that I inadvertently uploaded a slightly older copy of my project that required two minor changes:
1) Remove references to lFlags in the MemoryStream methods.
2) Change:
FileDescriptor.nFileSizeHigh = (Int32)(si.FileSize >> 32);
FileDescriptor.nFileSizeLow = (Int32)(si.FileSize & 0xFFFFFFFF);
to:
FileDescriptor.nFileSizeHigh = (UInt32)(si.FileSize >> 32);
FileDescriptor.nFileSizeLow = (UInt32)(si.FileSize & 0xFFFFFFFF);
The web page and ZIP file have been updated accordingly.
|
|
|
|
|
It works, thank you sooo much! You are freaking brilliant!
|
|
|
|
|