none
iTunesからの共有ファイルのドラッグ&ドロップ RRS feed

  • 質問

  • ファイル管理を行うWindowsアプリを作成していまして、ドラッグ&ドロップにも対応したいと考えています。

    エクスプローラからのドラッグ&ドロップは、以下のコードで実現できています。
      string[] sArray = (string[])e.Data.GetData("FileDrop");

    しかし、iTunesから「ファイル共有」の書類をドラッグ&ドロップした場合、上のコードの結果がnullになります。

    DataObjectのフォーマットを調べると以下の4つが見つかるので、
     FileContents
     FileGroupDescriptorW
     DragImageBits
     DragContext
    これらについて調べてみたのですが、ファイルのフルパスにあたる情報が見つかりませんでした。

    エクスプローラでも、フリーのファイル管理ソフトでも
    同様の処理ができているので何か方法はあると思うのですが分かりません。

    御存知の方いれば、ご助言をお願いします。


    ちなみに、iTunesの「プレイリスト」をドラッグ&ドロップしたの場合、先述のコードで取得できています。
    紛らわしいですが、「ファイル共有の書類」で壁に当たっています。

    環境は、WinXP SP3 + VS2005 SP2
    言語は、C# or VB.Net
    です。


    2012年1月5日 6:40

回答

  • ExplorerでZIPファイルを開くと圧縮されたファイルの一覧を見ることができますよね? でもこの時点で解凍されたわけではなく実在するファイルを表示しているわけでもありません。挙げられているDataObjectのフォーマットを見る限りiTunesのファイルの共有も同じことが言えて、実在するファイルを扱っているわけではないのでしょう。(ファイルが実在するかもしれないが、iTunesとしてはそのファイルにアクセスされたくないという意思表示。)

    で、こういう場合、FileGroupDescriptorWでファイル名を受け取ってFileContentsでファイルの中身を受け取るんだったと思います。Handling Shell Data Transfer Scenariosとかヒットしました。基本的にはnativeなCでの操作が想定されていて、C#など.NETからアクセスするのは厄介です…が、頑張れば何とかなったはずです。

    • 回答としてマーク HappyHill415 2012年1月6日 1:28
    2012年1月5日 8:02
  • 佐祐理さんありがとうございます。
    頂いたキーワードを頼りに再調査したところ、C++/CLIで似た投稿がありました。

    以下のコードでとりあえず、ドラッグしてファイルを保存できました。
    上の投稿でも結論が出ていないのですが、メモリの解放については不安があります。

    using ComTypes = System.Runtime.InteropServices.ComTypes;

    private void listView1_DragDrop(object sender, DragEventArgs e)
    {
        ComTypes.IDataObject idata = (ComTypes.IDataObject)e.Data;
        ComTypes.IEnumFORMATETC ienumFormat = idata.EnumFormatEtc(ComTypes.DATADIR.DATADIR_GET);

        // データの最大取得数をセット とりあえず200個
        const int count = 200;
        ComTypes.FORMATETC[] rgelt = new ComTypes.FORMATETC[count];
        int[] pceltFetched = new int[count];

        ienumFormat.Next(count, rgelt, pceltFetched);
        for (int i = 0; i < pceltFetched[0]; i++)
        {
            ComTypes.FORMATETC format = rgelt[i];

            // ここの判定はこれでいいのか?
            if (format.tymed == ComTypes.TYMED.TYMED_ISTREAM &&
                ((format.cfFormat & CF_SYLK) == 0) &&
                0 <= format.lindex)
            {

                ComTypes.IStream iStream = null;

                try
                {
                    ComTypes.STGMEDIUM medium;
                    idata.GetData(ref format, out medium);

                    iStream = (ComTypes.IStream)Marshal.GetObjectForIUnknown(medium.unionmember);

                    // データサイズの取得
                    ComTypes.STATSTG stat;
                    iStream.Stat(out stat, STATFLAG_NONAME);
                    // データの読み込み
                    byte[] pv = new byte[(int)stat.cbSize];
                    iStream.Read(pv, (int)stat.cbSize, IntPtr.Zero);
                    // ファイルへの書き込み (ファイル名を別途取得する必要あり)
                    string filePath = Path.Combine(m_resDirPath, format.lindex.ToString() + ".dat");
                    FileStream outfs = new FileStream(filePath, FileMode.Create);
                    outfs.Write(pv, 0, (int)stat.cbSize);
                    outfs.Close();
                }
                catch
                {
                }
                finally
                {
                    if (null != iStream)
                    {
                        Marshal.ReleaseComObject(iStream);
                        iStream = null;
                    }
                }
            }
        }
    }

    あと、ファイル名を取得するには、"FileGroupDescriptorW"を使うとのことでしたので、
    こちらを参考にしようかと思います。

     


    2012年1月10日 2:47

すべての返信

  • ExplorerでZIPファイルを開くと圧縮されたファイルの一覧を見ることができますよね? でもこの時点で解凍されたわけではなく実在するファイルを表示しているわけでもありません。挙げられているDataObjectのフォーマットを見る限りiTunesのファイルの共有も同じことが言えて、実在するファイルを扱っているわけではないのでしょう。(ファイルが実在するかもしれないが、iTunesとしてはそのファイルにアクセスされたくないという意思表示。)

    で、こういう場合、FileGroupDescriptorWでファイル名を受け取ってFileContentsでファイルの中身を受け取るんだったと思います。Handling Shell Data Transfer Scenariosとかヒットしました。基本的にはnativeなCでの操作が想定されていて、C#など.NETからアクセスするのは厄介です…が、頑張れば何とかなったはずです。

    • 回答としてマーク HappyHill415 2012年1月6日 1:28
    2012年1月5日 8:02
  • 回答ありがとうございます。

    iTunesからドラッグした場合のFileGroupDescriptorWの値を
    FILEGROUPDESCRIPTOR構造体に放り込むと、
    作成日時・更新日時・ファイルサイズ・ファイル名がセットされてました。
    コードでは実現できてませんが、ファイル名を取得するところまでは理解できました。

    ファイル名からファイルの実体を取得する部分が分かりません。
     e.Data.GetData("FileContents")
    はnullになっているので、ここからは何も取得できなさそうな気がします。

    この先の手法は御存知ではないでしょうか。

     

     

     

    2012年1月6日 1:27
  • 私の場合、iTunes側に相当するアプリを作ったことならあります。つまり今回のようにFileGroupDescriptorWやFileContentsを提供することで、実在しないファイルをWindows Explorerにドロップ可能なアプリケーションです。

    その際、一般的なSystem.Windows.Forms.IDataObjectではなくSystem.Runtime.InteropServices.ComTypes.IDataObjectを実装する必要がありました。今回のe.DataはSystem.Windows.Forms.IDataObjectインターフェースですが実際の型がDataObjectクラスならSystem.Runtime.InteropServices.ComTypes.IDataObjectインターフェースも実装していることになりますので、こちら側にキャストしてからGetData()を呼ぶことになるのか…な?

    2012年1月6日 1:55
  • 佐祐理さんありがとうございます。
    頂いたキーワードを頼りに再調査したところ、C++/CLIで似た投稿がありました。

    以下のコードでとりあえず、ドラッグしてファイルを保存できました。
    上の投稿でも結論が出ていないのですが、メモリの解放については不安があります。

    using ComTypes = System.Runtime.InteropServices.ComTypes;

    private void listView1_DragDrop(object sender, DragEventArgs e)
    {
        ComTypes.IDataObject idata = (ComTypes.IDataObject)e.Data;
        ComTypes.IEnumFORMATETC ienumFormat = idata.EnumFormatEtc(ComTypes.DATADIR.DATADIR_GET);

        // データの最大取得数をセット とりあえず200個
        const int count = 200;
        ComTypes.FORMATETC[] rgelt = new ComTypes.FORMATETC[count];
        int[] pceltFetched = new int[count];

        ienumFormat.Next(count, rgelt, pceltFetched);
        for (int i = 0; i < pceltFetched[0]; i++)
        {
            ComTypes.FORMATETC format = rgelt[i];

            // ここの判定はこれでいいのか?
            if (format.tymed == ComTypes.TYMED.TYMED_ISTREAM &&
                ((format.cfFormat & CF_SYLK) == 0) &&
                0 <= format.lindex)
            {

                ComTypes.IStream iStream = null;

                try
                {
                    ComTypes.STGMEDIUM medium;
                    idata.GetData(ref format, out medium);

                    iStream = (ComTypes.IStream)Marshal.GetObjectForIUnknown(medium.unionmember);

                    // データサイズの取得
                    ComTypes.STATSTG stat;
                    iStream.Stat(out stat, STATFLAG_NONAME);
                    // データの読み込み
                    byte[] pv = new byte[(int)stat.cbSize];
                    iStream.Read(pv, (int)stat.cbSize, IntPtr.Zero);
                    // ファイルへの書き込み (ファイル名を別途取得する必要あり)
                    string filePath = Path.Combine(m_resDirPath, format.lindex.ToString() + ".dat");
                    FileStream outfs = new FileStream(filePath, FileMode.Create);
                    outfs.Write(pv, 0, (int)stat.cbSize);
                    outfs.Close();
                }
                catch
                {
                }
                finally
                {
                    if (null != iStream)
                    {
                        Marshal.ReleaseComObject(iStream);
                        iStream = null;
                    }
                }
            }
        }
    }

    あと、ファイル名を取得するには、"FileGroupDescriptorW"を使うとのことでしたので、
    こちらを参考にしようかと思います。

     


    2012年1月10日 2:47
  • 先のコメントに書いたように、私はDragSource側を書いただけですので…その上で。

    Windows ExplorerはEnumFormatEtc()なんか確認せずにいきなりGetData()を呼び出してきてた気がします。その際に指定するFORMATETCはRegisterClipboardFormat()を使いました。

    CompTypes名前空間の各種型定義はnative Cの型定義とところどころ異なっています。(どう見てもバグにしか見えませんが、そう定義された以上、それが仕様です。)
    たとえばIDataObject.EnumFormatEtc()メソッドの戻り値の型はIEnumFORMATETCにもかかわらずS_OKなどが返されると嘘が書かれています。RegisterClipboardFormat()で返されるフォーマットIDはUINT型ですがそれを格納するFORMATETC.cfFormatはshortになっています。32bit符号なしと16bit符号ありです。キャストする際、符号拡張に注意しましょう。

    ComTypes.IStreamの解放が必要かどうか、よくわかりません。私自身はHGLOBALを使いました。尚、Marshal.AllocHGlobal()メソッドは名前に反してLocalAlloc()を使用します。

    追記:
    RegisterClipboardFormat()はウソでした。ソースコードを確認したところ
    static readonly DataFormats.Format CFSTR_FILEDESCRIPTORW = DataFormats.GetFormat( "FileGroupDescriptorW" );
    static readonly DataFormats.Format CFSTR_FILECONTENTS    = DataFormats.GetFormat( "FileContents" );
    
    と書いてました。
    • 編集済み 佐祐理 2012年1月10日 4:35
    2012年1月10日 4:06