.NET & Control > ドラック アンド ドロップ(Drag & Drop)
のぶ亭『プログラミングの相談窓口』 … 様々なプログラミング問題を個別対応致します |
ドラック アンド ドロップ(Drag & Drop)
Control.DoDragDrop メソッド
VSドキュメント
ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.ja/
fxref_system.windows.forms/html/ce4d509f-bb15-46f6-96ba-6fe913e236e8.htm
完全なソースコード有り。ただし、マウス ポインタ ファイルを指定するコードがあるが、ファイルがないため注意すること。
サンプルコードの実行イメージ
※フォームサイズ、Controlのレイアウト、フォントサイズなど変えている
このサンプルでは、左のListBoxをソースとし、右のListBoxをターゲットとしている。
つまり、左から右へのドラッグ アンド ドロップを実装した例である(逆には対応していない)。
ドラッグ アンド ドロップのイベント処理
Windowsはイベントのみを発行。それ以外の処理は、プログラムで実装する。つまり、「プロパティやメソッドを使用するとドラッグやドロップを簡潔に処理する」ということを期待してはいけない。
- 各種イベントから、マウスの状態や位置などを監視する。
- どの項目がドラッグ対象なのか、ドロップはどこに挿入するのか、または追加するのかをマウス座標から計算して処理を決定する。
- 個々のイベントを「連携したシナリオ」として考える必要がある。
このサンプルでは、アプリケーション内のControlを対象とした「ドラッグ アンド ドロップ」で、アプリケーション外からは操作できない。
このサンプルでは、独自のマウス ポインタを設定するコードがあるが、そもそもファイルが無いしため参考程度に見て欲しい。以下は、プログラム的にも問題があるためマスス ポインタの変更処理は割愛する。
このサンプルでは、コピーと移動に対応している。Ctrlキーを押下すると、ドラッグ アンド ドロップ時のマウス ポインタに[+]が追加される。移動の場合は、この[+]部分はないマウス ポインタになる。
- グローバル変数の初期化 ※2
- 「左側ListBox項目要素番号」を定義(ドラッグ側)
- 「右側ListBox項目要素番号」を定義(ドロップ側)
- 空の「MouseDown時の非ドラッグ領域」を作成
- 空の「画面作業領域原点(オフセット)」を作成
- 左側ListBoxのイベント
- MouseDown時 (マウス ダウン イベント)
- マウスの座標から、ListBoxの項目要素(0 から始まるインデックス番号)を検索
結果を「左側ListBox項目要素番号」にセットする
- もし、「左側ListBox項目要素番号」がListBox.NoMatches
でないならば(=有効)
(項目要素が見つかった)- マウスクリック位置とSystemInformation.DragSizeから「MouseDown時の非ドラッグ領域」を作成する
※3
- マウスクリック位置とSystemInformation.DragSizeから「MouseDown時の非ドラッグ領域」を作成する
※3
- 上記以外(項目要素が見つからなかった)
- 「MouseDown時の非ドラッグ領域」を空にする
- 「MouseDown時の非ドラッグ領域」を空にする
- マウスの座標から、ListBoxの項目要素(0 から始まるインデックス番号)を検索
- MouseMove時 (マウス ムーブ イベント)
- もし、マウスの左ボタンがクリックされていたら
- もし、「MouseDown時の非ドラッグ領域」が空でなく、かつマウスがこの領域に無い場合
(ドラッグできる状態)- 「画面作業領域原点(オフセット)」にSystemInformation.WorkingArea.Locationをセットする
- DoDragDrop
メソッドを呼び出して、ドラッグ アンド ドロップ操作を開始する ※6
- もし、DoDragDrop の実行結果が
DragDropEffects.Move の場合
- 左側のListBoxから「左側ListBox項目要素番号」の項目を削除する。
- もし、「左側ListBox項目要素番号」
> 0 の場合
- 「左側ListBox項目要素番号」を-1減算する
- 「左側ListBox項目要素番号」を-1減算する
- もし、左側ListBoxの項目数が > 0 の場合
- 左側ListBox項目要素 0番目を選択する
- 左側ListBox項目要素 0番目を選択する
- 左側のListBoxから「左側ListBox項目要素番号」の項目を削除する。
- 「画面作業領域原点(オフセット)」にSystemInformation.WorkingArea.Locationをセットする
- もし、「MouseDown時の非ドラッグ領域」が空でなく、かつマウスがこの領域に無い場合
- もし、マウスの左ボタンがクリックされていたら
- MouseUp時 (マウス アップ イベント)
- 「MouseDown時の非ドラッグ領域」を”空”にする
- 「MouseDown時の非ドラッグ領域」を”空”にする
- QueryContinueDrag時
- マウスがアプリケーションウィンドウからはみ出したら
e.Action に DragAction.Cancel をセットする(ドラッグ中止)
- マウスがアプリケーションウィンドウからはみ出したら
- GiveFeedback時
- <独自マウス ポインタの変更処理なので割愛>
- <独自マウス ポインタの変更処理なので割愛>
- MouseDown時 (マウス ダウン イベント)
- 右側ListBoxのイベント
- DragOver時
- もし、対象データが文字列でない場合
- ドロップ効果(e.Effect)にDragDropEffects.Noneをセットする
- Labelに "None - no
string data." をセットする
- 以下の処理は、キー状態とドラッグ元の操作を比較して、ドロップ効果(e.Effect)を作成する
キー状態は、※7を参照 - 「右側ListBox項目要素番号」を求める
マウス座標は、画面座標である。これをControl.PointToClient メソッドを使用して、クライアント座標に変換する。
その結果を、ListDragTarget.IndexFromPoint メソッドを使用して、項目要素番号を取得する。 - もし、「右側ListBox項目要素番号」がListBox.NoMatches
でないならば(=有効)
- LabelのTextに "Drops
before item #" をセットする #は挿入行番号
- LabelのTextに "Drops
before item #" をセットする #は挿入行番号
- 上記以外ならば
- LabelのTextに "Drops at
the end." をセットする
- LabelのTextに "Drops at
the end." をセットする
- もし、対象データが文字列でない場合
- DragDrop時
- もし、対象データが文字列の場合
- ドロップされた文字列(オブジェクト)を取得する
- もし、ドロップ効果(e.Effect)がコピーまたは移動の場合
- もし、「右側ListBox項目要素番号」がListBox.NoMatches
でないならば(=有効)
- 右側ListBoxに対して、「右側ListBox項目要素番号」に
文字列(オブジェクト)を挿入する
- 右側ListBoxに対して、「右側ListBox項目要素番号」に
- 上記以外ならば
- 右側ListBoxに対して、文字列(オブジェクト)を追加する
- 右側ListBoxに対して、文字列(オブジェクト)を追加する
- もし、「右側ListBox項目要素番号」がListBox.NoMatches
でないならば(=有効)
- ドロップされた文字列(オブジェクト)を取得する
- LabelのTextに "Drops at the
end." をセットする
- もし、対象データが文字列の場合
- DragEnter時
- Labelに "None" をセット
- Labelに "None" をセット
- DragLeave時
- Labelに "None" をセット
- DragOver時
ドラッグ アンド ドロップのイベント シナリオ
マウスの左Buttonクリック(Mouse Down)
マウスの左Button移動(Mouse Move)
ドラッグ中(GiveFeedback)
左側のListBox(ドラッグが可能)で、マウスをクリック
マウスをクリックしたまま(非ドラッグ領域を超えて)移動すると
補足(※)
- ドロップされるControlは、[AllowDrop]プロパティを True に設定すること。初期値は、False になっているので注意する。
- グローバル変数は宣言のみとなっている。実践では、初期化しておくことをお勧めする。
- [SystemInformation.DragSize]プロパティ
とは、(現在のシステム環境に関する情報)ドラッグ操作が開始されない範囲を示す、クリックしたポイントを中心とする四角形の幅と高さ と定義されている。従って、この四角形の領域は、ドラッグ操作を開始しない範囲を示す境界線として使用しなければならない。
ユーザーがマウス ボタンを押し下げている間にマウスが移動しても、この領域内にとどまっていればドラッグ操作は開始してはいけない。この処理により、ユーザーの意図しないドラッグ操作の発生を防止し、マウス ボタンをクリックして離す操作が容易になる。 - [Control.MousePosition]プロパティ とは、参照された時点のマウス カーソル位置(画面座標)を示す。
- [SystemInformation.WorkingArea]プロパティとは、画面の作業領域のサイズ
(ピクセル単位) のこと。アプリケーションで使用できる画面上の範囲を示す。
作業領域とは、(プライマリ モニタ)ディスプレイのデスクトップ領域からタスクバー、ドッキングされたウィンドウ、およびドッキングされたツール バーを除いた部分 と定義されている。
複数ディスプレイ環境で特定のモニタの作業領域が必要な場合、Screen.GetWorkingArea メソッドを使用する。 - [allowedEffects]パラメータに
DragDropEffects.All と
DragDropEffects.Link をセットしている。
- KeyState の意味
値 ビット キー 1 0 マウスの左ボタン 2 1 マウスの右ボタン 4 2 Shift キー 8 3 Ctrl キー 16 4 マウスの中央ボタン 32 5 Alt キー - ドラッグ可能な形式(DragOver時に取得)は以下の通り
string[] data = e.Data.GetFormats();
"Shell IDList Array" "DragImageBits" "DragContext" "DragSourceHelperFlags" "InShellDragLoop" "FileDrop" "FileNameW" "FileName" "IsShowingLayered" "DragWindow" "DropDescription" "DisableDragText" "IsShowingText"
拡張
このサンプルは、左のListBoxの項目を右のListBoxに移動またはコピーを行うサンプルである。が、イベントの引数にあるsender オブジェクトをキャストすることにより対象のControlを左右のListBoxに対応することができる。
ListBox ListSource = sender as ListBox
こうすることで、左右の相互移動やコピー、さらに自分自身の項目移動も可能になるが、現状のコードではMoveイベント処理に問題があるため改造する必要がある。
Microsoftの解説では、RichTextBoxにも触れていて、参考のコードも記載されている。上記のコードをそのまま流用しRichTextBoxコントロールを貼り付けたため、DragDropイベント処理を以下の様に変更。
private
void
richTextBox1_DragDrop(object
sender,
DragEventArgs e)
{
richTextBox1.Text = (String)e.Data.GetData("Text");
}
注意)
RichTextBoxコントロールは、デザイン時の「プロパティ ウィンドウ」から、ドラッグ アンド ドロップの機能を隠蔽している。そのため、richTextBox1.AllowDrop = true; を手作業で記述し、イベントハンドラも追加する必要がある。
Formにドラッグされたファイル名を取得する方法
- ドロップされることを有効にするため、該当Formの[AllowDrop]プロパティを True に変更する。
- 以下の2つのメソッド コードをテンプレートとして追加する。
private void Form1_DragOver(object sender, DragEventArgs e) { if (!e.Data.GetDataPresent("FileDrop")) { e.Effect = DragDropEffects.None; return; } if ((e.KeyState & 4) == 4 && (e.AllowedEffect & DragDropEffects.Move) == DragDropEffects.Move) { e.Effect = DragDropEffects.Move; } if ((e.KeyState & 8) == 8 && (e.AllowedEffect & DragDropEffects.Copy) == DragDropEffects.Copy) { e.Effect = DragDropEffects.Copy; } else if ((e.AllowedEffect & DragDropEffects.Move) == DragDropEffects.Move) { e.Effect = DragDropEffects.Move; } else e.Effect = DragDropEffects.None; } }
private void Form1_DragDrop(object sender, DragEventArgs e) { if (e.Data.GetDataPresent("FileDrop")) { String[] msg = (String[])e.Data.GetData("FileDrop"); if (e.Effect == DragDropEffects.Copy || e.Effect == DragDropEffects.Move) { MessageBox.Show("\n" + msg[0]); <このコードを変更する> } } }
- Form1_DragOver と Form1_DragDrop
のイベントハンドラをForm1に追加する。つまり、
Formのプロパティウィンドウより、EventのDragOver と DragDropに対して、上記のイベントハンドラを割り当てる。
この処理で、ドロップされたファイル名一覧が取得できるので、後はプログラムで処理で自由に処理を行えばいい。