...ing logging 4.0

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

DFL: ドラッグアンドドロップ(3)~IDropSource編~

前回、IDropTarget インタフェースを使って、自アプリへのドロップを実装しました。

受け渡すデータに対応する IDataObject インタフェースを実装したクラス、ドロップ先に対応する IDropTarget インタフェースを実装したクラス、ドラッグ元に対応する IDragSource インタフェースを実装したクラスがあれば、OLE DnD が一通り使えます。

今回は、IDropSource インタフェースを使って、自アプリからのドラッグを実装します。

DnD で受け渡せるデータの定義方法はほかにもありますが、DFL では IDataObject インタフェースを実装した DataObject クラスにデータを格納して受け渡す方法だけが用意されているようです。

IDragSource インタフェースが持つメソッドはこの2つです。

  • giveFeedBack (IDropSource::GiveFeedback)
  • queryContinueDrag (IDropSource::QueryContinueDrag)

ドロップ先のテストとしては、色々なデータを受け取ってくれるワードパッドを使います。

OLE ドラッグの実装

import dfl;

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

class MainForm : Form
{
    private Label _label;

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

        _label = new Label();
        _label.parent = this;
        _label.location = Point(0,0);
        _label.dock = DockStyle.FILL;
        _label.font = new Font("Meiryo UI", 14f);

        _label.text = "これはOLEドラッグ対応のラベルです。\n" ~ 
                      "このエリアからワードパッドへドラッグしてください。\n";

        // マウスボタンを押したらドラッグ開始する
        _label.mouseDown ~= (Control sender, MouseEventArgs e) {
            // 受け渡すデータの入れ物を用意する
            DataObject dataObj = new DataObject();
            // 今回はラベルの文字列を受け渡すデータとして用意した
            string txt = _label.text;
            // DataFormats.stringFormatはドロップターゲットが文字列を受け取るときに指定する
            // Dataクラスのコンストラクタは色々な型に対応している(string[]もOK)
            dataObj.setData(DataFormats.stringFormat, new Data(txt));
            // ドラッグ開始する
            // 今回はDragDropEffects.COPYモードのドラッグだけ許可する
            DragDropEffects effect = _label.doDragDrop(dataObj, DragDropEffects.COPY);
        };
        // ドロップ先にマウスカーソルが表示されるときの見た目を変える
        _label.giveFeedback ~= (Control sender, GiveFeedbackEventArgs e) {
            e.useDefaultCursors = true; // デフォルト
            // ほかのカーソルを使うときは...
            //
            // e.useDefaultCursors = false;
            // if ((e.effect & DragDropEffects.COPY) == DragDropEffects.COPY)
            // Cursor.current = ...; // カーソルの画像リソースを用意する必要あり
            // else ...
        };
        // ドラッグ中の出来事に対応する(ドラッグをキャンセルするなど)
        _label.queryContinueDrag ~= (Control sender, QueryContinueDragEventArgs e) {
            if(e.escapePressed)
            {
                e.action = DragAction.CANCEL; // ESCキーを押したらキャンセル
            }
            else if ((e.keyState & DragDropKeyStates.RIGHT_MOUSE_BUTTON) == DragDropKeyStates.RIGHT_MOUSE_BUTTON)
            {
                e.action = DragAction.CANCEL; // 右クリックを押したらキャンセル
            }
        };
    }
}

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

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

これで、OLE によるドラッグアンドドロップできました。

いつもどおり WInForms とほとんど同じですので、詳しい説明はコード中のコメントに任せます。

なお、DnD で受け渡すあらゆるデータを保持するために Data クラスが用意されているのは、前回説明したとおりです。

GitHub のサンプルコード

GitHub の DFL に同梱されたサンプルコードの方には、ListBox を OLE DnD に対応させた DragDropListBox を実装して公開しています。 本家 WinForms の ListView にはあって ListBox にはない、便利な itemDrag イベントハンドラを定義してあります。 DragDropListBox は、その辺りを考慮して、ウィンドウプロシージャでマウスの位置とボタンの状態を監視して状態遷移処理をしているので流れが複雑ですが、まあまあ直感的な操作感にできていると思います。 本当なら、ListBox のアイテム状態には、解除状態と選択状態のほかに、ListView にある解除待ち状態も欲しいところです。

リストボックスのドラッグアンドドロップ問題

先行研究1先行研究2によれば、ListBox を複数選択モードにしてドラッグに対応させるのは鬼門のようです。 selectedItems() と selectedIndices() での選択中項目の取得がうまくいかなかったり、例外が出てしまうそうですが、原因としていくつか可能性らしきものは掴んだので、今度、書こうかなと思います。 大元は、ドラッグ開始時に発生する WM_LBUTTONDOWN を ListBox のデフォルトウィンドウプロシージャに与えてはいけないのだと推測しています。

DFL のダウンロード

github.com

参考文献