You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 11:15:44