WPF向Windows资源管理器拖拽文件无效果问题排查
WPF Shell风格文件拖拽问题:拖拽显示成功但文件未复制
在WPF应用中实现向Windows资源管理器的Shell风格文件拖拽操作,代码如下,但拖拽时资源管理器显示成功(hResult返回262400),但文件并未被复制,怀疑是DROPFILES结构体的文件路径处理有问题,请求排查方向。
private void OnMouseDown(object sender, MouseButtonEventArgs e) { DependencyObject src = (DependencyObject)(e.OriginalSource); while (!(src is Control)) { src = VisualTreeHelper.GetParent(src); } string clicked_element_name = src.GetType().Name; if (clicked_element_name != "ListViewItem") { this.ExListView.UnselectAll(); this.ExListView.Focus(); } else { ListViewItem item = src as ListViewItem; FileExplorerItem explorerItem = item.Content as FileExplorerItem; CDataObject dataObject = new CDataObject(); if (dataObject != null) { List<string> filePaths = GetSelectedListViewItemsPaths(); String fileList = ""; foreach (string filePath in filePaths) { fileList += filePath + char.MinValue; } fileList += char.MinValue; // Allocate memory for the DROPFILES structure int size = Marshal.SizeOf(typeof(DROPFILES)) + (fileList.Length + 2) *2; // Length of the string plus one for the double null terminator IntPtr hGlobal = Marshal.AllocHGlobal(size); // Initialize the DROPFILES structure DROPFILES dropFiles = new DROPFILES(); unsafe { dropFiles.pFiles = sizeof(DROPFILES); // Offset to the file list } dropFiles.fWide = true; // Indicates that the file list is in Unicode format byte[] dropFilesBuffer = new byte[size]; IntPtr dropFilesPtr = Marshal.AllocHGlobal(Marshal.SizeOf(dropFiles)); Marshal.StructureToPtr(dropFiles, dropFilesPtr, true); Marshal.Copy(dropFilesPtr, dropFilesBuffer, 0, Marshal.SizeOf(dropFiles)); Marshal.FreeHGlobal(dropFilesPtr); // Copy the file list to the global memory byte[] fileListBuffer = Encoding.Unicode.GetBytes(fileList.ToString()); Marshal.Copy(fileListBuffer, 0, hGlobal + Marshal.SizeOf(typeof(DROPFILES)), fileListBuffer.Length); // Copy the DROPFILES structure to the global memory Marshal.Copy(dropFilesBuffer, 0, hGlobal, dropFilesBuffer.Length); System.Runtime.InteropServices.ComTypes.FORMATETC formatec = new System.Runtime.InteropServices.ComTypes.FORMATETC(); formatec.dwAspect = System.Runtime.InteropServices.ComTypes.DVASPECT.DVASPECT_CONTENT; formatec.lindex = -1; formatec.tymed = System.Runtime.InteropServices.ComTypes.TYMED.TYMED_HGLOBAL; formatec.cfFormat = (short)DataFormats.GetDataFormat(DataFormats.FileDrop).Id; System.Runtime.InteropServices.ComTypes.STGMEDIUM stgmedium = new System.Runtime.InteropServices.ComTypes.STGMEDIUM(); stgmedium.tymed = System.Runtime.InteropServices.ComTypes.TYMED.TYMED_HGLOBAL; stgmedium.unionmember = hGlobal; IntPtr hwnd = new WindowInteropHelper(Application.Current.MainWindow).Handle; dataObject.SetData(ref formatec, ref stgmedium, true); try { int hr = DragHelper.SHDoDragDrop(hwnd, dataObject, null, DragDropEffects.Copy, out var result); if (hr < 0) { throw Marshal.GetExceptionForHR(hr); } } catch (Exception ex) { MessageBox.Show(ex.Message); } Marshal.FreeHGlobal(hGlobal); } else { // handle the case when dataObject is null } } }
核心问题排查与修复
1. DROPFILES结构体定义错误(最可能原因)
Windows API的DROPFILES结构体中,pFiles字段是ULONG_PTR类型,用来适配32/64位系统。如果将其定义为int,在64位进程中会导致字段长度不足(仅4字节,实际需要8字节),偏移地址被截断,资源管理器无法找到文件列表的正确位置。
正确的结构体定义:
[StructLayout(LayoutKind.Sequential)] private struct DROPFILES { public int x; public int y; [MarshalAs(UnmanagedType.Bool)] public bool fNC; [MarshalAs(UnmanagedType.Bool)] public bool fWide; public IntPtr pFiles; // 用IntPtr适配32/64位系统 }
设置pFiles时无需unsafe块,直接用结构体的实际内存大小:
dropFiles.pFiles = new IntPtr(Marshal.SizeOf(typeof(DROPFILES)));
2. 文件列表内存分配与写入问题
- 内存大小计算冗余:
(fileList.Length + 2)*2是错误的,fileList已经包含了所有终止符,Encoding.Unicode.GetBytes(fileList)的长度就是fileList.Length * 2,正确的内存大小应为:int size = Marshal.SizeOf(typeof(DROPFILES)) + fileList.Length * 2; - 地址计算用
IntPtr.Add更安全,避免64位系统下的地址偏移错误:IntPtr fileListPtr = IntPtr.Add(hGlobal, Marshal.SizeOf(typeof(DROPFILES))); Marshal.Copy(fileListBuffer, 0, fileListPtr, fileListBuffer.Length);
3. 基础校验
- 确认
GetSelectedListViewItemsPaths()返回的是绝对路径,且文件/文件夹真实存在,资源管理器不会复制不存在的文件,也不会抛出错误。 - 若无需依赖Shell扩展的特殊行为,可直接使用WPF内置的拖拽简化实现,避免手动构造非托管内存:
var dataObject = new DataObject(DataFormats.FileDrop, filePaths.ToArray()); DragDrop.DoDragDrop(ExListView, dataObject, DragDropEffects.Copy);
内容的提问来源于stack exchange,提问作者Kevin Kloimwieder
相关产品推荐
相关产品推荐

