« OLEドラッグ&ドロップ対応にする(テキスト編) | トップページ | OLEドラッグ&ドロップ対応にする方法(オブジェクト編) »

2011年1月28日 (金)

OLEドラッグ&ドロップ対応にする方法(ファイル編)

前回のテキスト編に続き、今回はファイルを対象としたドラッグ&ドロップを行なってみます。

ファイルのドロップターゲットになるアプリケーションは多いものの、ドロップソースとしての機能を持つものは、エクスプローラ的なもの位しかない様な気もします。しかし、ここではあえて、TreeView や ListView は使いません。これらは、別枠で記事にしたいと思っていますので、ここでは ListBox を使用する事にします。

またファイルの場合、ドロップターゲットとしてだけ機能すればいいケースが多い筈なので、今回はドロップソースと、ドロップターゲットを別プロジェクトにしてみました。

1.ファイルのドロップターゲットとする場合の記述
既に、テキスト編で基本的な説明を行なっているので、違う部分だけ説明します。
まず、一般的には、エクスプローラからファイルをドラッグ&ドロップにて受け取る訳ですが、このときデータの形式は、DataFormats.FileDrop で指定します。また、ドラッグしているファイル名を取得する場合には、GetData メソッドを使用しますが、その戻り値の型は、文字列の配列 (string[]) となります。

では、フォーム中のリストボックス(listBox1)を、ファイルのドロップターゲットして定義してみたソースを示します。

private void Form1_Load(object sender, System.EventArgs e)
{
    listBox1.AllowDrop = true;
}

private void listBox1_DragEnter(object sender, DragEventArgs e)
{
    if (e.Data.GetDataPresent(DataFormats.FileDrop))
        e.Effect = DragDropEffects.Copy;
}

private void listBox1_DragDrop(object sender, DragEventArgs e)
{
    if (!e.Data.GetDataPresent(DataFormats.FileDrop)) return;
    string[] files = e.Data.GetData(DataFormats.FileDrop, true) as string[];
    listBox1.Items.Clear();
    listBox1.Items.AddRange(files);
}

テキスト編と同様に、ここでは listBox1.AlloDrop プロパティを true にする事を明示する為、フォームの Load イベントで AllowDrop の設定を行なっています。

エクスプローラからファイルをドラッグ中の状態で、カーソルをデスクトップに持っていくと Ctrl, Shift, Alt の各キーを押す事により、マウスカーソルが変化します。これと同じ様な事を行なう場合は、DragEnter でなく、DragOver イベントで処理を行ないます(DragEnter に同じ処理を入れてもいいのですが、実質的には意味がない様です)。

private void lstTarget_DragOver(object sender, DragEventArgs e)
{
    if (e.Data.GetDataPresent(DataFormats.FileDrop))
    {
        if ((e.KeyState & 32) > 0)       // Alt入力中
            e.Effect = DragDropEffects.Link;
        else if ((e.KeyState & 4) > 0)   // Shift入力中
            e.Effect = DragDropEffects.Move;
        else
            e.Effect = DragDropEffects.Copy;
    }
}

キーのコンビネーションの場合(例えば Alt + Shift キーを押した状態)、デスクトップ上でドラッグ中のときのカーソルとは違ったりしますが、まあこんな感じで出来ます。

2.ファイルのドロップソースとする場合の記述
ここでは、フォーム上のリストボックス(listBox1)に、既にファイルパスを表示した項目一覧が表示されている前提とします。
テキスト編と同様、まずドロップソースとするコントロールの MouseDown / MouseMove / MouseUp イベントを使用して、ドラッグ開始の判断を行ないます。

private Rectangle dragRangeRectangle = Rectangle.Empty;
private void listBox1_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
    {
        Size dragSize = SystemInformation.DragSize;
        dragRangeRectangle = new Rectangle(new Point(
            e.X - (dragSize.Width /2), e.Y - (dragSize.Height /2)), dragSize);
    }
}

private void listBox1_MouseMove(object sender, MouseEventArgs e)
{
    // マウス左ボタン押下状態でなければ処理しない
    if (e.Button != MouseButtons.Left) return;
    // dragRangeRectangle 未設定なら処理しない
    if (dragRangeRectangle == Rectangle.Empty) return;
    // ドラッグ状態と判断する範囲を超えていない
    if (dragRangeRectangle.Contains(e.X, e.Y)) return;
    // 選択中Itemがない場合はドラッグ処理しない
    if (listBox1.SelectedItems.Count == 0) return;
    // ドラッグ開始の処理
    string[] files = new string[listBox1.SelectedItems.Count];
    for (int i = 0; i < listBox1.SelectedItems.Count; ++i)
        files[i] = listBox1.SelectedItems[i].ToString();
    DataObject fileData = new DataObject(DataFormats.FileDrop, files);
    DragDropEffects dde = listBox1.DoDragDrop(fileData, DragDropEffects.All);
    dragRangeRectangle = Rectangle.Empty;
}           

private void listBox1_MouseUp(object sender, MouseEventArgs e)
{
    dragRangeRectangle = Rectangle.Empty;
}

また、マウス右クリックによる、ドラッグ中止をサポートしたい場合も、QueryContinueDrag イベントに、テキスト編と同様の処理を記述します。

private void listBox1_QueryContinueDrag(object sender, QueryContinueDragEventArgs e)
{
    if ((e.KeyState & 2) > 0)    // マウス右ボタン押下
        e.Action = DragAction.Cancel;
}

これで、ドロップソースとして、「一応」機能する様になります。「一応」と書いたのは、例外が発生する場合がある為ですが、この件については、次の項にて説明します。
さてここで、1.で作成したドロップターゲット用アプリケーションにドラッグ&ドロップすると、ドロップターゲット側では、リストボックスにドロップしたファイルのファイルパスが文字列として項目に追加されます。その様にプログラムを指定しているので当然ですね。ただ DragDropEffects を意識した処理は特にありません。従って、「複写」か「移動」か「リンク付け」かは、カーソル形状が変更される以外、何も変わらないものでした。
では、今度はエクスプローラに対して、ドラッグ&ドロップしてみましょう。すると、今度は実際にファイルが複写されたり、移動されたり、ショートカットが作成されたりします。OLEドラッグ&ドロップが動作しているのが、実感出来ると思います。

3.ListBox の選択中項目をドラッグ対象にする不具合
テキスト編でも紹介した、DOBON.NETの「Drag&Dropを行う」の最後の方にある補足で、.Net Framework1.1では、ListBox の選択中項目(SelectedIndices、SelectedItems等)が正常に動作しないとあります。実際私の環境でも、IndexOutOfRangeException が時々発生しています。通常は、SelectedIndices や SelectedItems はきちんと動作しているのに、何故なのでしょうかね。
これが、.Net Framework2.0でも発生するのかははっきりしません。今のところ、私の環境では起きていないのですが、C#2003でも頻発する時もあれば、かなり安定して動作する事もある現象なので、断言出来ないです。
安全策をとるなら、DOBON.NETでやっている様に SelectedItems は使用せずに、「マウスボタンダウンの位置のListBox 項目をドラッグ対象にする」方法をとるしかないのではないかと思います。しかし、これだと項目のない空き領域でマウスボタンダウンした場合は、選択中の項目があっても、ドラッグしない事になるし、何より複数の項目選択(MultiSelect)が出来ない事になってしまいますね。
.NET でドラッグ&ドロップをやる場合、ListBox をドロップソースにするのは避けるのが賢明かもしれません。

4.デフォルト以外のカーソル表示
ドロップターゲット側の DropEnter または DropOver イベントにて、DragEventArgs.Effect に対し、DragDropEffects の指定を行なう事で、カーソル形状を変更出来ました。これを見る限り、具体的なカーソル形状の指定まで、ドロップターゲット側で制御しているかの錯覚を起こしそうです。
しかし、実際はそうではなく、ドロップソース側で指定します。具体的には、GiveFeedback イベント中で、DragDropEffects の状態により、Cursor.Current プロパティの指定を行ないます。

ここで、DragDropEffects の効果に合わせ、"copy.cur", "move.cur" のファイル名で、カーソルリソースが用意されている前提で、以下の様な記述を行なってみました。なお、DragDropEffects.Link のときは、ちょっと指定を変えて、標準で用意されているカーソル(Cursors.AppStarting)を指定しています。

private void listBox1_GiveFeedback(object sender, GiveFeedbackEventArgs e)
{
    e.UseDefaultCursors = false;
    System.Reflection.Assembly asm =
        System.Reflection.Assembly.GetExecutingAssembly();
    if ((e.Effect & DragDropEffects.Copy) > 0)
        Cursor.Current = new Cursor(
            asm.GetManifestResourceStream("DropSource.copy.cur"));
    else if ((e.Effect & DragDropEffects.Move) > 0)
        Cursor.Current = new Cursor(
            asm.GetManifestResourceStream("DropSource.move.cur"));
    else if ((e.Effect & DragDropEffects.Link) > 0)
        Cursor.Current = Cursors.AppStarting;
    else
        e.UseDefaultCursors = true;
}

これにより、ファイルをドラッグ中のドロップターゲット側では、この GiveFeedback イベントで指定されたカーソルが表示される様になります。

なお今回のサンプルソースでは、ここでこれまで取り上げた内容に加え、アニメーションカーソルを指定したバージョンも作成してみたので、興味ある人はダウンロードして遊んでみて下さい。

では、そのサンプルソース一式です。
「DropFile.cab」をダウンロード


« OLEドラッグ&ドロップ対応にする(テキスト編) | トップページ | OLEドラッグ&ドロップ対応にする方法(オブジェクト編) »

C# Tips」カテゴリの記事

コメント

コメントを書く

コメントは記事投稿者が公開するまで表示されません。

(ウェブ上には掲載しません)

トラックバック


この記事へのトラックバック一覧です: OLEドラッグ&ドロップ対応にする方法(ファイル編):

« OLEドラッグ&ドロップ対応にする(テキスト編) | トップページ | OLEドラッグ&ドロップ対応にする方法(オブジェクト編) »