WisdomSoft - for your serial experiences.

4.3 ドラッグ・アンド・ドロップ

ファイルエクスプローラーなどから、ユーザーが選択したデーターをコントロールにドロップできるプログラムを作成します。コントロールがデータのドラッグやドロップを感知する方法を解説します。

4.3.1 ファイルをドラッグする

多くビジネスアプリケーションツールは、Windows 上のファイルエクスプローラからファイルをウィンドウにドラッグ・アンド・ドロップすると、選択したファイルを開くという機能をサポートしています。例えば「メモ帳」のようなツールも「ファイル」メニューの「開く」からダイアログでファイルを選択することができますが、多くのユーザーはテキストファイルをメモ帳にドラッグ・アンド・ドロップしてファイルを開いていることでしょう。ほとんどの人にとって、この操作の方が簡単で直感的なのです。

ゲームプログラミングでこのようなシステムとの連携は必要ないため、ドラッグ・アンド・ドロップが必要になることは少ないかもしれませんが、システムと連帯するゲームやゲーム開発ツールそのものを作る場合はやはり必要になるでしょう。もちろん、本書で紹介している技術はゲーム以外の分野でも利用できるので、見識を広げる目的でこの機能を学習するのもひとつです。

ドラッグ・アンド・ドロップは、ご存知、アイコンなどのオブジェクト上でマウスボタンを押してそのままカーソルを移動させ(ドラッグ)、別の場所でボタンを離す(ドラッグ)する一連の動作のことです。ファイルなどのオブジェクトの移動や、コントロールやアプリケーション間のデータ交換によく使われる動作です。

ウィンドウ上でドラッグ・アンド・ドロップがされているかどうかを判断する方法も、マウスやキーボードと同様にイベントで監視します。ただし、Control クラスはデフォルトでドラッグイベントを受け付けないように設定されているため、AllowDrop プロパティを設定する必要があります。 

Control クラス AllowDrop プロパティ
public virtual bool AllowDrop {get; set;}

デフォルトでは false が設定されています。ドラッグイベントを受けたい場合、このプロパティを true に設定します。

ユーザーが、データをコントロール上にドラッグすると、最初に OnDragEnter() メソッドが呼び出され、OnDragEnter() メソッドは DragEnter イベントを発生させます。

Control クラス OnDragEnter() メソッド
protected virtual void OnDragEnter(DragEventArgs drgevent)
Control クラス DragEnter イベント
public event DragEventHandler DragEnter

続いて、マウスカーソルがドラッグ状態のままコントロール上を移動すると OnDragOver() メソッドが呼び出され、OnDragOver() メソッドは DragOver イベントを発生させます。

Control クラス OnDragOver() メソッド
protected virtual void OnDragOver(DragEventArgs drgevent)
Control クラス DragOver イベント
public event DragEventHandler DragOver

DragEnter イベントと DragOver イベントに追加できるのは System.Windows.Forms.DragEventHandler デリゲート型のオブジェクトです。

System.Windows.Forms.DragEventHandler デリゲート
[Serializable]
public delegate void DragEventHandler(object sender, DragEventArgs e);

OnDragOver() メソッドや DragEventHandler デリゲートで受け取る e パラメータは System.Windows.Forms.DragEventArgs クラスのオブジェクトです。このクラスは、ドラッグイベントに関する情報を提供します。

System.Windows.Forms.DragEventArgs クラス
[ComVisible(true)]
public class DragEventArgs : EventArgs

ドラッグイベントはマウスイベントと同様にマウスカーソルの移動に関する情報が含まれているため、イベントが発生した座標を提供しています。X 座標は X プロパティから、Y 座標は Y プロパティから取得することができます。

DragEventArgs クラス X プロパティ
public int X { get; }
DragEventArgs クラス Y プロパティ
public int Y { get; }

ただし、この座標はクライアント座標ではなくスクリーン座標が格納されているので描画処理などに利用する場合は注意してください。

最後に、ドラッグ中のカーソルがコントロールの外に出たり、ドラッグをキャンセルすると OnDragLeave() メソッドが呼び出され、OnDragLeave() メソッドは DragLeave イベントを発生させます。

Control クラス OnDragLeave() メソッド
protected virtual void OnDragLeave(EventArgs e)
Control クラス DragLeave イベント
public event EventHandler DragLeave

OnDragLeave() メソッドと DragLeave イベントは、ドラッグされているカーソルがコントロールから離れたときに発生するイベントなので、座標などのドラッグ情報は含まれていません。そのため、受け取るイベントオブジェクト e は、イベントデータを格納する基本クラスである System.EventArgs クラス型のオブジェクトです。

System.EventArgs クラス
[Serializable]
public class EventArgs

EventArgs は、MouseEventArgs クラスなど、他のイベント情報クラスのルートとなる基本クラスに過ぎないので、提供する情報はありません。同様に DragLeave イベントは System.EventHandler デリゲート型となります。これは、特にイベントデータを持たないイベントを処理するメソッドを表します。

System.EventHandler デリゲート
[Serializable]
public delegate void EventHandler(object sender, EventArgs e)

ここまでの流れをまとめると、これらのイベントは次のような流れで発生します。

  • ドラッグカーソルが入った:OnDragEnter() → DragEnter
  • ドラッグカーソルが移動:OnDragOver() → DragOver
  • ドラッグカーソルが出た:OnDragLeave() → DragLeave
図1 ドラッグイベントの遷移
図1 ドラッグイベントの遷移

これらのイベントを受けることで、ユーザーがファイルなどのデーターをコントロール上でドラッグしていることを感知できます。この時点では、まだドロップしたデータを処理しません。

コード1
using System;
using System.Drawing;
using System.Windows.Forms;

public class Test : Form
{
	private string text;

	private void Test_DragOver(object sender, DragEventArgs e)
	{
		text = "DragOver:X=" + e.X + ", Y=" + e.Y;
		Invalidate();
	}

	private void Test_DragLeave(object sender, EventArgs e)
	{
		text = "DragLeave";
		Invalidate();
	}

	protected override void OnPaint(PaintEventArgs e)
	{
		base.OnPaint (e);
		Font font = new Font(Font.Name, 20);
		Brush brush = new SolidBrush(Color.Black);
		e.Graphics.DrawString(text, font, brush, 0, 0);
	}

	public Test() 
	{
		AllowDrop = true;
		DragOver += new DragEventHandler(Test_DragOver);
		DragLeave += new EventHandler(Test_DragLeave);
	}

	static void Main() 
	{
		Application.Run(new Test());
	}
}
実行結果
コード1 実行結果

コード1は、コントロール上をドラッグ状態で移動したときに発生する DragOver イベントと、コントロールからドラッグカーソルが出た場合に発生する DragLeave イベントを処理しています。ドラッグの移動に興味が無い場合は DragEnter イベントを使うこともできます。

4.3.2 ドロップされたデータを取得する

コード1はドラッグ状態でコントロールの上を通過するとドラッグイベントを受けることができましたが、ドロップされた情報に関しては受け付けていませんでした。ドロップは単純な情報ではなく、Windows エクスプローラーからファイルがドロップされる以外にも、ドラッグに対応している他のアプリケーションから情報が送られてくる可能性もあります。これを処理する必要があるので、ドロップから情報を受けるのは多少の手続きが必要になります。

コントロール上でドロップされると OnDragDrop() メソッドが呼び出され、このメソッドは DragDrop イベントを発生させます。

Control クラス OnDragDrop() メソッド
protected virtual void OnDragDrop(DragEventArgs drgevent)
Control クラス DragDrop イベント
public event DragEventHandler DragDrop

OnDragDrop() メソッドと DragDrop イベントの使い方は OnDragOver() メソッドや DragOver イベントとまったく同じです。ただし、デフォルトの状態ではドラッグをしようとしてもイベントが発生しません。デフォルトでは、ドラッグイベントに対してドロップを受け付けない設定になっているためです。

コントロールがドロップを受け付けるには DragEventArgs クラスの Effect プロパティを設定する必要があります。

DragEventArgs クラス Effect プロパティ
public DragDropEffects Effect { get; set; }

このプロパティに設定する値は、ドラッグ・アンド・ドロップ操作の効果を表す System.Windows.Forms.DragDropEffects 列挙体のいずれかのメンバです。

System.Windows.Forms.DragDropEffects 列挙体
[Flags]
[Serializable]
public enum DragDropEffects

一般にドロップの効果として用いられるコピーは Copy メンバを、データの移動が目的であれば Move メンバを指定するという形になります。デフォルトではデータを受け付けない None が設定されています。ドロップに対応するにはドラッグイベントを受けたときにパラメータから取得した DragEventArgs オブジェクトの Effect プロパティを適切な値に設定してください。

しかし、正しくドロップを受け入れるにはドロップされようとしているデータがアプリケーションが対応可能なデータ形式かどうかを調べる必要があります。ドラッグされているデータソースを調べるには DragEventArgs クラスの Data プロパティからデータを取得します。

DragEventArgs クラス Data プロパティ
public IDataObject Data { get; }

このプロパティは、System.Windows.Forms.IDataObject インターフェイス型のオブジェクトを提供します。このインタフェースは特定の形式に依存しない任意の形式のデータ転送をサポートします。

System.Windows.Forms.IDataObject インターフェイス
[ComVisible(true)]
public interface IDataObject

データソースはネイティブの Windows から提供されるため、元のデータ形式が明確に固定されているわけではありません。そのため、こちらが対応可能なデータフォーマットであるかどうかを GetDataPresent() メソッドで調べます。

IDataObject インタフェース GetDataPresent() メソッド
bool GetDataPresent(string format)

format パラメータには格納されているデータ形式の文字列表現を設定します。テキストやイメージなど、基本的なデータ形式は System.Windows.Forms.DataFormats クラスで定義されているので、このクラスから取得します。

System.Windows.Forms.DataFormats クラス
System.Object
    System.Windows.Forms.DataFormats
public class DataFormats

DataFormats クラスは、定義済みのデータ形式の文字列表現を静的な読み取り専用公開フィールドで提供しています。テキストなら Text フィールド、ビットマップイメージならば Bitmap フィールド、エクスプローラーなどからのファイルドロップであれば FileDrop フィールドに一致します。

データがアプリケーション対応の形式であれば、Effect プロパティを設定してドロップを受け入れます。ドロップされたデータを受け取るには GetData() メソッドからデータ形式を指定して取得します。

IDataObject インターフェイス GetData() メソッド
object GetData(string format)

format パラメータには GetDataPresent() メソッドと同様にデータフォーマットを表す文字列を指定します。通常は DataFormats クラスのフィールドから定義済みのデータ形式を使うことになるでしょう。戻り値はデータ型によって異なります。FileDrop の場合は、ファイルパスを格納する string 型の配列が返ります。ファイルドロップは複数のファイルが選択されている可能性があるので配列となります。

コード2
using System.Drawing;
using System.Windows.Forms;

public class Test : Form
{
	private string[] dropFiles;

	private void Test_DragDrop(object sender, DragEventArgs e)
	{
		dropFiles = (string [])e.Data.GetData(DataFormats.FileDrop);
		Invalidate();
	}

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

	protected override void OnPaint(PaintEventArgs e)
	{
		base.OnPaint (e);
		if (dropFiles == null) return;

		Brush brush = new SolidBrush(Color.Black);
		for(int i = 0; i < dropFiles.Length ; i++) 
		{
			e.Graphics.DrawString(dropFiles[i], Font, brush, 0, Font.Height * i);
		}
	}

	public Test() 
	{
		AllowDrop = true;
		DragDrop += new DragEventHandler(Test_DragDrop);
		DragEnter += new DragEventHandler(Test_DragEnter);
	}

	static void Main() 
	{
		Application.Run(new Test());
	}
}
実行結果
コード2 実行結果 コード2 実行結果

コード2は、ウィンドウにドロップされたファイルを列挙するプログラムです。複数のファイルを選択してドロップされる可能性もあるので配列に対応します。結果は、配列の要素ごとに改行してドロップされたファイルをすべて表示します。

ファイルがウィンドウにドラッグされると DragEnter イベントが発生します。DragEnter イベントでは、ドラッグされているデータ形式に対応できるかどうかを GetDataPresent() メソッドで調べ、指定した形式のデータを持っている場合はドロップイベントを許可するために Effect に Copy を設定します。

これで、ドロップイベントを受けられるようになったので、エクスプローラーからファイルをウィンドウにドロップすると DragDrop イベントが発生します。このイベントでは単純にドロップで渡されたファイルパスの配列をフィールド dropFiles に保存しています。OnPaint() メソッドではこのフィールドの内容にしたがってウィンドウのパスを列挙しています。