WPF(.NET4.8)如何处理Windows资源管理器中ZIP预览文件的拖放?
处理Windows资源管理器中ZIP内文件的拖放
当从Windows资源管理器预览的未解压ZIP中拖出文件时,WPF的DataObject.GetData(DataFormats.FileDrop)只会返回临时文件的路径,无法直接获取父ZIP路径和拖放文件的相对路径。要解决这个问题,需要借助Windows Shell的COM接口和P/Invoke来解析拖放数据中的Shell命名空间项信息。
核心思路
Windows资源管理器拖放ZIP内文件时,会在DataObject中提供Shell IDList Array格式的数据,这些数据对应Shell命名空间中的条目(包含ZIP文件本身和内部文件的层级关系)。通过COM接口IShellItemArray和IShellItem可以获取到条目所属的父ZIP文件路径,以及内部文件的相对路径。
具体实现步骤
1. 定义必要的COM接口和P/Invoke函数
首先需要定义Windows Shell相关的COM接口和API函数,用于解析拖放数据:
using System; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Interop; // COM接口定义 [ComImport] [Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface IShellItem { void BindToHandler(IntPtr pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid bhid, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv); void GetParent(out IShellItem ppsi); void GetDisplayName(SIGDN sigdnName, out IntPtr ppszName); void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs); void Compare(IShellItem psi, uint hint, out int piOrder); } [ComImport] [Guid("B63EA76D-1F85-456F-A19C-48159EFA858B")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface IShellItemArray { void BindToHandler(IntPtr pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid bhid, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv); void GetPropertyStore(int flags, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv); void GetPropertyDescriptionList(IntPtr keyType, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv); void GetAttributes(uint dwAttribFlags, uint sfgaoMask, out uint psfgaoAttribs); void GetCount(out uint pdwNumItems); void GetItemAt(uint dwIndex, out IShellItem ppsi); } // 用于获取显示名称的枚举 public enum SIGDN : uint { SIGDN_FILESYSPATH = 0x80058000, // 获取文件系统路径(ZIP文件本身会返回实际路径,内部文件返回临时路径) SIGDN_RELATIVEPARENT = 0x80080001, // 获取相对于父项的路径 } // P/Invoke函数 public class NativeMethods { [DllImport("shell32.dll", PreserveSig = false)] public static extern void SHCreateShellItemArrayFromDataObject(IntPtr pdo, out IShellItemArray ppsia); }
2. 在拖放事件中解析数据
在WPF控件的Drop事件中,获取COM的IDataObject实例,然后通过SHCreateShellItemArrayFromDataObject创建ShellItem数组,遍历每个ShellItem获取所需信息:
private void OnDrop(object sender, DragEventArgs e) { if (!e.Data.GetDataPresent("Shell IDList Array")) { // 处理普通文件拖放(原有逻辑) var files = e.Data.GetData(DataFormats.FileDrop) as string[]; if (files != null) { // 你的原有处理逻辑 } return; } // 获取COM的IDataObject var comDataObject = (IDataObject)e.Data.GetData(typeof(IDataObject)); NativeMethods.SHCreateShellItemArrayFromDataObject(Marshal.GetComInterfaceForObject(comDataObject, typeof(IDataObject)), out var shellItemArray); if (shellItemArray == null) return; shellItemArray.GetCount(out var itemCount); for (uint i = 0; i < itemCount; i++) { shellItemArray.GetItemAt(i, out var shellItem); if (shellItem == null) continue; // 获取父ShellItem(即ZIP文件) shellItem.GetParent(out var parentShellItem); if (parentShellItem != null) { // 获取ZIP文件的系统路径 parentShellItem.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out var zipPathPtr); var zipFilePath = Marshal.PtrToStringUni(zipPathPtr); Marshal.FreeCoTaskMem(zipPathPtr); // 获取拖放文件相对于ZIP的路径 shellItem.GetDisplayName(SIGDN.SIGDN_RELATIVEPARENT, out var relativePathPtr); var relativeFilePath = Marshal.PtrToStringUni(relativePathPtr); Marshal.FreeCoTaskMem(relativePathPtr); // 这里可以处理获取到的ZIP路径和相对路径 Console.WriteLine($"ZIP文件路径:{zipFilePath}"); Console.WriteLine($"相对路径:{relativeFilePath}"); Marshal.ReleaseComObject(parentShellItem); } Marshal.ReleaseComObject(shellItem); } Marshal.ReleaseComObject(shellItemArray); }
3. 注意事项
- 确保项目引用了
System.Runtime.InteropServices。 - 拖放操作需要控件的
AllowDrop属性设为true,同时处理DragEnter事件并设置e.Effect = DragDropEffects.Copy。 - 当拖放多个ZIP内文件时,每个文件的父项都是对应的ZIP文件,需要分别处理。
检查DataObject中的所有格式
如果不确定拖放数据包含哪些格式,可以在DragEnter或Drop事件中打印所有可用格式,方便排查:
var formats = e.Data.GetFormats(); foreach (var format in formats) { Console.WriteLine($"可用格式:{format}"); }
内容的提问来源于stack exchange,提问作者Poulpynator
相关产品推荐
相关产品推荐

