...ing logging 4.0

はてなブログに移行しました。D言語の話とかいろいろ。

DFL: ドラッグアンドドロップ(1)~DragAcceptFiles編~

ソースはリンク切れのため今となっては詳細不明。

DFLドラッグアンドドロップがうまく動かないという話はずっと前に聞いていたのですが、自分で使ったことがなかったためそのままでしたので、今回どうなっているのか試してみました。

調べたところ、Windowsドラッグアンドドロップは、実装方法が2つあって、DragAcceptFiles() を使ってファイルのドロップだけに対応する簡単な方法と、あらゆるドラッグとドロップの両方に対応する OLE Drag & Drop (DnD) があるようです。

DFL は OLE DnD をサポートしているようですが、ファイルをウィンドウにドロップしても、正しいファイル名が取得できませんでした。 検証するためには COM の壁も超えないといけなくて調べることが多く大変なので、まずは簡単な方で実験しました。

下のウィンドウにエクスプローラーからファイルをドロップすると、ファイルパス一覧が描画されます。

import dfl;
import std.conv : to;

version(Have_dfl) // For DUB.
{
}
else
{
    pragma(lib, "dfl.lib");
}

import core.sys.windows.shellapi; // DragAcceptFiles, DragQueryFile, DragFinish, HDROP
import core.sys.windows.winuser; // WM_DROPFILES

class MainForm : Form
{
    private Label _label;

    public this()
    {
        this.text = "Drag-and-Drop example";
        this.size = Size(600, 300);

        // wndProc()でWM_DROPFILESメッセージをトラップできるようにする
        DragAcceptFiles(handle, true);

        _label = new Label();
        _label.location = Point(0,0);
        _label.autoSize = true;
        _label.dock = DockStyle.FILL;
        _label.font = new Font("Meiryo UI", 14f);
        _label.text = "Drop files to this form.";
        _label.parent = this;
    }

    protected override void wndProc(ref Message msg)
    {
        switch (msg.msg)
        {
            case WM_DROPFILES:
                HDROP hDrop = cast(HDROP)msg.wParam;
                
                // ドロップしたファイル数を得る
                uint numFiles = DragQueryFile(hDrop, -1, null, 0);
                if (numFiles == 0)
                {
                    _label.text = "Error";
                }
                else
                {
                    _label.text = "";
                    for (int i; i < numFiles; i++)
                    {
                        enum BUFFER_LENGTH = 260;
                        wchar[BUFFER_LENGTH] fileName;
                        // ドロップしたファイル1つずつのパスをメモリにコピーする
                        DragQueryFile(hDrop, i, fileName.ptr, BUFFER_LENGTH);
                        _label.text = _label.text ~ to!string(fileName);
                        _label.text = _label.text ~ "\n";
                    }
                }
                // ファイルパスを受け取るために確保されたメモリを解放する
                DragFinish(hDrop);
                break;
         default:
        }
        super.wndProc(msg);
    }
}

static this()
{
    Application.enableVisualStyles();
}

void main()
{
    Application.run(new MainForm());
}

簡単な方法だけあって、特に難しいことはありませんでした。

後々分かったのですが、HDROP 型のハンドルには、受け取ったファイルパスが直列に並んで書き込まれているメモリ領域が保持されているようです。 "filepath\0filepath\0filepath\0\0" のように、最後は "\0\0" で終わります。 きっと、DragQueryFile() がファイルパス1つずつの切り出しをしているのでしょうね。

fileName が wchar[] なのは DragQueryFile() が wchar* を求めるから。 to!string(fileName) しているのは、Label.text() が string を求めるからです。 ansi/unicode の設定をちゃんとすれば自分で変換するのは不要かもしれません(手抜き)。

あと、dfl.internal.utf.dragQueryFile() という関数が用意されているのに気がつきました。こっちを使ってもいいかもしれません。

簡単な方法なので、このままだとできないことが色々ありそうです。

  1. ファイル以外のドロップができない(OLE DnD が必要)。
  2. 自ウィンドウからドラッグを開始できない(OLE DnD の IDropSource インタフェースの実装が必要)。
  3. キー入力(shift, ctrl, alt)と組み合わせてドロップのモード(move, copy, link など)を選択できない(OLE DnD の IDropTarget インタフェースの実装が必要)。

DFL のダウンロード

github.com

OLE DnD でのファイルドロップに対応したバージョンを既に公開しています。 今回のサンプルコードと似たようなサンプルも公開済みです。

参考文献