63769


お世話になっております。  
C#で下記コードでクリップボードへファイルをコピーしています。

[code]

    using System.IO;
    using System.Windows.Forms;
    
    string[] FileList = {"D:\\test1.txt"};
    IDataObject data = new DataObject();
    byte[] bs = new byte[] { (byte)DragDropEffects.Copy, 0, 0, 0 };
    MemoryStream ms = new MemoryStream(bs);
    data.SetData("Preferred DropEffect", ms);
    data.SetData(DataFormats.FileDrop, ファイル一覧);
    Clipboard.SetDataObject(data, true);
    
[/code]

この状態で、Explorerで「D:\」を開いて貼り付け操作を行うと、「送り側と受け側のファイル名が同じです。」というエラーが表示されてしまいます。  
しかし、Explorerで直接コピーして貼り付けると、「test1 - コピー.txt」のようにファイル名を自動的に補正して貼り付けてくれると思います。  
C#でクリップボードにコピーしたファイルをExplorerで貼り付けた際、このようにファイル名を自動的に補正して貼り付けさせるようにする方法はあるのでしょうか。  
また、多くのアプリケーションでは、ファイルをコピーした際に「Shell IDList
Array」というフォーマットが追加されるようですが、C#でこれを実装することはできるのでしょうか。  
以上、よろしくお願いいたします。


CC BY-SA 4.0
2020-03-10T08:03:37.053
63727
2020-03-12T23:08:08.993
2020-03-10T08:29:12.083
29034
29034
question
c#
C#でのクリップボードへのファイルのコピーについて


「Shell IDList
Array」とは[クリップボードフォーマットの`CFSTR_SHELLIDLIST`](https://docs.microsoft.com/en-
us/windows/win32/shell/clipboard#cfstr_shellidlist)のことで、ネイティブ形式のCIDA構造体を作る必要があり、CIDA構造体の中には`LPCITEMIDLIST`をいろいろ納めなければならなかったりとで、かなり大変です。

とはいえ、[`SHCreateDataObject`](https://docs.microsoft.com/en-
us/windows/win32/api/shlobj_core/nf-shlobj_core-shcreatedataobject)という「Shell
IDList
Array」を含んだIDataObjectを作るAPIが用意されているので、頑張ればなんとかなります。また、`LPCITEMIDLIST`の方もいくつかAPIが用意されているので適切に組み合わせればなんとかなります。

また.NET Frameworkではネイティブの[`IDataObject`](https://docs.microsoft.com/en-
us/windows/win32/api/objidl/nn-objidl-
idataobject)をラップした[`IDataObject`](https://docs.microsoft.com/ja-
jp/dotnet/api/system.windows.forms.idataobject?view=netframework-4.8)が使用されていますが、継承関係にあると共に、[`DataObject`コンストラクタ](https://docs.microsoft.com/ja-
jp/dotnet/api/system.windows.forms.dataobject.-ctor?view=netframework-4.8#System_Windows_Forms_DataObject__ctor_System_Object_)や[`Clipboard.SetDataObject`](https://docs.microsoft.com/en-
us/dotnet/api/system.windows.forms.clipboard.setdataobject?view=netframework-4.8)などはネイティブの`IDataObject`も受け付けてくれるのでそのまま渡すことができます。

[code]

    using IComDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
    
    [DllImport("Shell32.dll")]
    static extern IntPtr ILClone(IntPtr pidl);
    [DllImport("Shell32.dll", CharSet = CharSet.Unicode)]
    static extern IntPtr ILCreateFromPathW(string pszPath);
    [DllImport("Shell32.dll")]
    static extern IntPtr ILFindLastID(IntPtr pidl);
    [DllImport("Shell32.dll")]
    static extern bool ILRemoveLastID(IntPtr pidl);
    [DllImport("Shell32.dll", PreserveSig = false)]
    static extern void SHCreateDataObject(IntPtr pidlFolder, int cidl, IntPtr[] apidl, IComDataObject pdtInner, in Guid riid, out IComDataObject ppv);
    
    static (IntPtr, IntPtr) Divide(IntPtr pidl) {
        var dir = ILClone(pidl);
        ILRemoveLastID(dir);
        var item = ILClone(ILFindLastID(pidl));
        return (dir, item);
    }
    
    static void SetFile(string fullpath) {
        var pidl = ILCreateFromPathW(fullpath);
        var (dir, item) = Divide(pidl);
        SHCreateDataObject(dir, 1, new[] { item }, null, typeof(IComDataObject).GUID, out var comDataObject);
        Clipboard.SetDataObject(comDataObject, true);
        Marshal.FreeCoTaskMem(item);
        Marshal.FreeCoTaskMem(dir);
        Marshal.FreeCoTaskMem(pidl);
    }
    
[/code]

* * *

[`CFSTR_SHELLIDLIST`](https://docs.microsoft.com/en-
us/windows/win32/shell/clipboard#cfstr_shellidlist)は絶対パスとそこからの相対パスで表現されます。それを受けて先に使用した[`SHCreateDataObject`](https://docs.microsoft.com/en-
us/windows/win32/api/shlobj_core/nf-shlobj_core-
shcreatedataobject)は絶対パスとその直下に含まれる子アイテムが要求されます。そのため、異なるディレクトリに存在するファイルは表現することができません。  
`SHCreateDataObject`の前身で現在は動作保証のない[`CIDLData_CreateFromIDArray`](https://docs.microsoft.com/en-
us/windows/win32/api/shlobj_core/nf-shlobj_core-
cidldata_createfromidarray)であれば絶対パスとそこからの相対パスを要求するため、複数ファイルを楽に表現できます。

[code]

    using IComDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
    
    [DllImport("Shell32.dll", PreserveSig = false)]
    static extern void CIDLData_CreateFromIDArray(IntPtr pidlFolder, int cidl, IntPtr[] apidl, out IComDataObject ppdtobj);
    [DllImport("Shell32.dll", CharSet = CharSet.Unicode)]
    static extern IntPtr ILCreateFromPathW(string pszPath);
    [DllImport("Shell32.dll")]
    static extern IntPtr ILFindChild(IntPtr pidlParent, IntPtr pidlChild);
    [DllImport("Shell32.dll", PreserveSig = false)]
    static extern void SHGetFolderLocation(IntPtr hwnd, int csidl, IntPtr hToken, int dwFlags, out IntPtr ppidl);
    const int CSIDL_DESKTOP = 0;
    
    static void SetFiles(params string[] files) {
        SHGetFolderLocation(IntPtr.Zero, CSIDL_DESKTOP, IntPtr.Zero, 0, out var desktop);
        var absolutes = Array.ConvertAll(files, file => ILCreateFromPathW(file));
        var relatives = Array.ConvertAll(absolutes, absolute => ILFindChild(desktop, absolute));
        CIDLData_CreateFromIDArray(desktop, relatives.Length, relatives, out var comDataObject);
        Clipboard.SetDataObject(comDataObject, true);
        Array.ForEach(absolutes, absolute => Marshal.FreeCoTaskMem(absolute));
        Marshal.FreeCoTaskMem(desktop);
    }
    
[/code]


CC BY-SA 4.0
2020-03-11T13:54:27.520
63769
2020-03-12T23:08:08.993
2020-03-12T23:08:08.993
4236
4236
63727
answer