使用 IStream 拖放虚拟文件
Posted
技术标签:
【中文标题】使用 IStream 拖放虚拟文件【英文标题】:Drag and drop virtual files using IStream 【发布时间】:2010-11-14 07:39:32 【问题描述】:我想启用从基于 Windows 窗体的应用程序到 Windows 资源管理器的拖放功能。大问题:文件存储在数据库中,所以我需要使用延迟数据渲染。有一个article on codeproject.com,但作者使用的是 H_GLOBAL 对象,这会导致文件大于 aprox 的内存问题。 20 MB。我还没有找到使用 IStream 对象的可行解决方案。我认为这一定是可以实现的,因为这不是一个不寻常的情况。 (例如,FTP 程序也需要这样的功能)
编辑:是否可以在用户删除文件时获取事件?所以我可以例如将它复制到 temp 并且资源管理器从那里获取它?也许我的问题有另一种方法......
【问题讨论】:
【参考方案1】:AFAIK,没有关于.net 的工作文章。所以你应该自己写,这有点复杂,因为.net DataObject 类是有限的。我有相反任务的工作示例(接受来自资源管理器的延迟渲染文件),但它更容易,因为我不需要自己的 IDataObject 实现。
所以你的任务是:
-
在 .net 中查找有效的 IDataObject 实现。我推荐你看here (Shell Style Drag and Drop in .NET (WPF and WinForms))
您还需要一个用于托管流的 IStream 包装器(实现起来相对容易)
使用来自MSDN (Shell Clipboard Formats) 的信息实现延迟渲染
这是一个起点,并且通常提供足够的信息来实现此类功能。有一点耐心和几次不成功的尝试,你会做到的:)
更新:以下代码缺少很多必要的方法和函数,但主要逻辑在此。
// ...
private static IEnumerable<IVirtualItem> GetDataObjectContent(System.Windows.Forms.IDataObject dataObject)
if (dataObject == null)
return null;
List<IVirtualItem> Result = new List<IVirtualItem>();
bool WideDescriptor = dataObject.GetDataPresent(ShlObj.CFSTR_FILEDESCRIPTORW);
bool AnsiDescriptor = dataObject.GetDataPresent(ShlObj.CFSTR_FILEDESCRIPTORA);
if (WideDescriptor || AnsiDescriptor)
IDataObject NativeDataObject = dataObject as IDataObject;
if (NativeDataObject != null)
object Data = null;
if (WideDescriptor)
Data = dataObject.GetData(ShlObj.CFSTR_FILEDESCRIPTORW);
else
if (AnsiDescriptor)
Data = dataObject.GetData(ShlObj.CFSTR_FILEDESCRIPTORA);
Stream DataStream = Data as Stream;
if (DataStream != null)
Dictionary<string, VirtualClipboardFolder> FolderMap =
new Dictionary<string, VirtualClipboardFolder>(StringComparer.OrdinalIgnoreCase);
BinaryReader Reader = new BinaryReader(DataStream);
int Count = Reader.ReadInt32();
for (int I = 0; I < Count; I++)
VirtualClipboardItem ClipboardItem;
if (WideDescriptor)
FILEDESCRIPTORW Descriptor = ByteArrayHelper.ReadStructureFromStream<FILEDESCRIPTORW>(DataStream);
if (((Descriptor.dwFlags & FD.FD_ATTRIBUTES) > 0) && ((Descriptor.dwFileAttributes & FileAttributes.Directory) > 0))
ClipboardItem = new VirtualClipboardFolder(Descriptor);
else
ClipboardItem = new VirtualClipboardFile(Descriptor, NativeDataObject, I);
else
FILEDESCRIPTORA Descriptor = ByteArrayHelper.ReadStructureFromStream<FILEDESCRIPTORA>(DataStream);
if (((Descriptor.dwFlags & FD.FD_ATTRIBUTES) > 0) && ((Descriptor.dwFileAttributes & FileAttributes.Directory) > 0))
ClipboardItem = new VirtualClipboardFolder(Descriptor);
else
ClipboardItem = new VirtualClipboardFile(Descriptor, NativeDataObject, I);
string ParentFolder = Path.GetDirectoryName(ClipboardItem.FullName);
if (string.IsNullOrEmpty(ParentFolder))
Result.Add(ClipboardItem);
else
VirtualClipboardFolder Parent = FolderMap[ParentFolder];
ClipboardItem.Parent = Parent;
Parent.Content.Add(ClipboardItem);
VirtualClipboardFolder ClipboardFolder = ClipboardItem as VirtualClipboardFolder;
if (ClipboardFolder != null)
FolderMap.Add(PathHelper.ExcludeTrailingDirectorySeparator(ClipboardItem.FullName), ClipboardFolder);
return Result.Count > 0 ? Result : null;
// ...
public VirtualClipboardFile : VirtualClipboardItem, IVirtualFile
// ...
public Stream Open(FileMode mode, FileAccess access, FileShare share, FileOptions options, long startOffset)
if ((mode != FileMode.Open) || (access != FileAccess.Read))
throw new ArgumentException("Only open file mode and read file access supported.");
System.Windows.Forms.DataFormats.Format Format = System.Windows.Forms.DataFormats.GetFormat(ShlObj.CFSTR_FILECONTENTS);
if (Format == null)
return null;
FORMATETC FormatEtc = new FORMATETC();
FormatEtc.cfFormat = (short)Format.Id;
FormatEtc.dwAspect = DVASPECT.DVASPECT_CONTENT;
FormatEtc.lindex = FIndex;
FormatEtc.tymed = TYMED.TYMED_ISTREAM | TYMED.TYMED_HGLOBAL;
STGMEDIUM Medium;
FDataObject.GetData(ref FormatEtc, out Medium);
try
switch (Medium.tymed)
case TYMED.TYMED_ISTREAM:
IStream MediumStream = (IStream)Marshal.GetTypedObjectForIUnknown(Medium.unionmember, typeof(IStream));
ComStreamWrapper StreamWrapper = new ComStreamWrapper(MediumStream, FileAccess.Read, ComRelease.None);
// Seek from beginning
if (startOffset > 0)
if (StreamWrapper.CanSeek)
StreamWrapper.Seek(startOffset, SeekOrigin.Begin);
else
byte[] Null = new byte[256];
int Readed = 1;
while ((startOffset > 0) && (Readed > 0))
Readed = StreamWrapper.Read(Null, 0, (int)Math.Min(Null.Length, startOffset));
startOffset -= Readed;
StreamWrapper.Closed += delegate(object sender, EventArgs e)
ActiveX.ReleaseStgMedium(ref Medium);
Marshal.FinalReleaseComObject(MediumStream);
;
return StreamWrapper;
case TYMED.TYMED_HGLOBAL:
byte[] FileContent;
IntPtr MediumLock = Windows.GlobalLock(Medium.unionmember);
try
long Size = FSize.HasValue ? FSize.Value : Windows.GlobalSize(MediumLock).ToInt64();
FileContent = new byte[Size];
Marshal.Copy(MediumLock, FileContent, 0, (int)Size);
finally
Windows.GlobalUnlock(Medium.unionmember);
ActiveX.ReleaseStgMedium(ref Medium);
Stream ContentStream = new MemoryStream(FileContent, false);
ContentStream.Seek(startOffset, SeekOrigin.Begin);
return ContentStream;
default:
throw new ApplicationException(string.Format("Unsupported STGMEDIUM.tymed (0)", Medium.tymed));
catch
ActiveX.ReleaseStgMedium(ref Medium);
throw;
// ...
【讨论】:
感谢您的回答。我已经尝试过自己实现它,但这很棘手。不幸的是,Explorer.exe 总是关闭。但我会再试一次并用一些源代码更新我的帖子。 你能给我你的实现吗? (Alltough 是做相反的任务,它可能会有所帮助)谢谢。 你能发布你的“我有相反任务的工作示例(接受来自资源管理器的延迟渲染文件)”吗?【参考方案2】:Google 员工可能会觉得这很有用:download a file using windows IStream
【讨论】:
以上是关于使用 IStream 拖放虚拟文件的主要内容,如果未能解决你的问题,请参考以下文章
使用 PowerShell 创建 ISO 映像:如何将 IStream 保存到文件?