...ing logging 4.0

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

DFL: ドラッグアンドドロップ(2)~IDropTarget編~

前回、DragAcceptFiles() を使い、自アプリへのファイルのドロップを簡単に実装できましたが、この方法ではできないことが色々あります。

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

そこで、allowDrop プロパティで OLE DnD を有効にして、OLE でのドロップを使います。 これにより、ファイル以外のドロップができ、キー入力と組み合わせてドロップのモードを変更することができます。

なお、まだ OLE Drag は含まれないので、自ウィンドウからドラッグ開始はできません。

DFL の Control クラスは COM の IDropTarget インタフェースを実装しているので、それを継承している Form やその他のコントロールも同様にドロップ先になれます。

Control クラスには、IDropTarget インタフェースのメソッドにそれぞれ対応するイベントが用意されています。

  • dragEnter (IDropTarget::DragEnter)
  • dragOver (IDropTarget::DragOver)
  • dragLeave (IDropTarget::DragLeave)
  • drapDrop (IDropTarget::Drop)

OLE ドロップの実装

import dfl;

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

class MainForm : Form
{
    private Label _label;

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

        this.allowDrop = true; // フォームがドロップを受け付けるように設定する
        
        // フォームエリアにドラッグして入ってきたとき
        this.dragEnter ~= (Control sender, DragEventArgs e) {
            // 何もすることなし
        };
        // フォームエリア内でドラッグ中のとき
        this.dragOver ~= (Control sender, DragEventArgs e) {
            // ドラッグ中のオブジェクトに応じた処理を書く
            // fileDropを与えて呼び出すとファイルをドロップできるフォームになる
            // ドロップしたいオブジェクトによって適宜変える
            if (e.data.getDataPresent(DataFormats.fileDrop))
            {
                if ((e.keyState & DragDropKeyStates.SHIFT_KEY) == DragDropKeyStates.SHIFT_KEY)
                {
                    // ドラッグ中にSHIFTキーを押したら移動アイコンにする
                    e.effect = DragDropEffects.MOVE;
                }
                else if ((e.keyState & DragDropKeyStates.ALT_KEY) == DragDropKeyStates.ALT_KEY)
                {
                    // ドラッグ中にALTキーを押したらリンクアイコンにする
                    e.effect = DragDropEffects.LINK;
                }
                else
                {
                    // ドラッグ中にCTRLキーを押したらコピーアイコンにする
                    e.effect = DragDropEffects.COPY;
                }
                assert((e.allowedEffect & e.effect) != 0);
            }
            else
            {
                // 受け取れないものをドラッグしているときはドロップ不可アイコンにする
                e.effect = DragDropEffects.NONE;
            }
        };
        // ドロップしたとき
        this.dragDrop ~= (Control sender, DragEventArgs e) {
            // ドロップしたファイル一覧を得る
            string[] files = e.data.getData(DataFormats.fileDrop, false).getStrings;
            _label.text = "";
            foreach (string fileName; files)
            {
                _label.text = _label.text ~ fileName ~ "\n";
            }
        };
        // ドラッグ中にフォームエリアから出たとき
        this.dragLeave ~= (Control sender, EventArgs e) {
            // 何もすることなし
        };

        _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;
    }
}

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

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

いつもどおり基本的には WinForms と同じです。

完全に作りかけだったのか一時期は動いていたのか分かりませんが、どうも OLE DnD のコードは完全に壊れていました。 キャストしてコンパイルが通るようにしただけだったというか、なんかすごいことになっていました。

ドロップするあらゆるデータを Data クラスが保持するようになっており、Data クラスは、データそのものと、そのデータの型を覚えています。 データそのものは、int とか Object とか string とか ubyte[] とかの中身です。 これを全部 void* で指しておいて、後で復元できるようにデータの型を別に記憶していたようですが、ぶっ壊れていました。

とりあえずここを直すために、void* で保持することをやめて、保持できるデータ型ごとに別のメンバ変数を設けてやっつけました。 うまくいけば、Phobos の SumType や Variant が綺麗に使えるところかもしれません。

DFLのダウンロード

github.com

参考文献