ここではドラッグ&ドロップの例として、あるリストボックス内のアイテムをもう一つのリストボックスにドラッグ&ドロップにより移動させる方法を紹介します。
まずは、ドロップを受け入れるリストボックスのAllowDropプロパティをTrueにしておきます。
実際にドラッグを開始するには、ドラッグされるアイテムのあるリストボックスのDoDragDropメソッドを呼び出します。ドロップを受け入れるリストボックスでは、リストボックス内にドラッグされた時にDragEnterイベントが発生しますので、ここでドロップを受け入れるか、受け入れるとしたらどのように受け入れるか(コピーか移動か等)を決定します。リストボックスにドロップされるとDragDropイベントが発生しますので、ここでドロップされたデータを取得し、リストボックスへデータを追加します。
次のコードでは、フォーム(Form1)にリストボックスが2つあり(ListBox1,ListBox2)、ListBox1のアイテムをドラッグ&ドロップによりListBox2に移動できるようにしています。
'フォームのLoadイベントハンドラ Private Sub Form1_Load(ByVal sender As Object, _ ByVal e As EventArgs) Handles MyBase.Load 'ひとつのアイテムしか選択できないようにする ListBox1.SelectionMode = SelectionMode.One ListBox2.SelectionMode = SelectionMode.One 'ListBox1にアイテムを追加 Dim i As Integer For i = 0 To 4 ListBox1.Items.Add(i.ToString()) Next i 'ListBox2にアイテムを追加 For i = 5 To 9 ListBox2.Items.Add(i.ToString()) Next i 'ListBox2へのドロップを受け入れる ListBox2.AllowDrop = True 'ListBox1のイベントハンドラを追加 AddHandler ListBox1.MouseDown, AddressOf ListBox1_MouseDown 'ListBox2のイベントハンドラを追加 AddHandler ListBox2.DragEnter, AddressOf ListBox2_DragEnter AddHandler ListBox2.DragDrop, AddressOf ListBox2_DragDrop End Sub 'ListBox1でマウスボタンが押された時 Private Sub ListBox1_MouseDown( _ ByVal sender As Object, ByVal e As MouseEventArgs) 'マウスの左ボタンだけが押されている時のみドラッグできるようにする If e.Button = MouseButtons.Left Then 'ドラッグの準備 Dim lbx As ListBox = CType(sender, ListBox) 'ドラッグするアイテムのインデックスを取得する Dim itemIndex As Integer = lbx.IndexFromPoint(e.X, e.Y) If itemIndex < 0 Then Return End If 'ドラッグするアイテムの内容を取得する Dim itemText As String = CStr(lbx.Items(itemIndex)) 'ドラッグ&ドロップ処理を開始する Dim dde As DragDropEffects = _ lbx.DoDragDrop(itemText, DragDropEffects.All) 'ドロップ効果がMoveの時はもとのアイテムを削除する If dde = DragDropEffects.Move Then lbx.Items.RemoveAt(itemIndex) End If End If End Sub 'ListBox2内にドラッグされた時 Private Sub ListBox2_DragEnter( _ ByVal sender As Object, ByVal e As DragEventArgs) 'ドラッグされているデータがstring型か調べ、 'そうであればドロップ効果をMoveにする If e.Data.GetDataPresent(GetType(String)) Then e.Effect = DragDropEffects.Move Else 'string型でなければ受け入れない e.Effect = DragDropEffects.None End If End Sub 'ListBox2にドロップされたとき Private Sub ListBox2_DragDrop( _ ByVal sender As Object, ByVal e As DragEventArgs) 'ドロップされたデータがstring型か調べる If e.Data.GetDataPresent(GetType(String)) Then Dim target As ListBox = CType(sender, ListBox) 'ドロップされたデータ(string型)を取得 Dim itemText As String = _ CStr(e.Data.GetData(GetType(String))) 'ドロップされたデータをリストボックスに追加する target.Items.Add(itemText) End If End Sub
//フォームのLoadイベントハンドラ private void Form1_Load(object sender, System.EventArgs e) { //ひとつのアイテムしか選択できないようにする ListBox1.SelectionMode = SelectionMode.One; ListBox2.SelectionMode = SelectionMode.One; //ListBox1にアイテムを追加 for (int i = 0; i < 5; i++) ListBox1.Items.Add(i.ToString()); //ListBox2にアイテムを追加 for (int i = 5; i < 10; i++) ListBox2.Items.Add(i.ToString()); //ListBox2へのドロップを受け入れる ListBox2.AllowDrop = true; //ListBox1のイベントハンドラを追加 ListBox1.MouseDown += new MouseEventHandler(ListBox1_MouseDown); //ListBox2のイベントハンドラを追加 ListBox2.DragEnter += new DragEventHandler(ListBox2_DragEnter); ListBox2.DragDrop += new DragEventHandler(ListBox2_DragDrop); } //ListBox1でマウスボタンが押された時 private void ListBox1_MouseDown(object sender, MouseEventArgs e) { //マウスの左ボタンだけが押されている時のみドラッグできるようにする if (e.Button == MouseButtons.Left) { //ドラッグの準備 ListBox lbx = (ListBox) sender; //ドラッグするアイテムのインデックスを取得する int itemIndex = lbx.IndexFromPoint(e.X, e.Y); if (itemIndex < 0) return; //ドラッグするアイテムの内容を取得する string itemText = (string) lbx.Items[itemIndex]; //ドラッグ&ドロップ処理を開始する DragDropEffects dde = lbx.DoDragDrop(itemText, DragDropEffects.All); //ドロップ効果がMoveの時はもとのアイテムを削除する if (dde == DragDropEffects.Move) lbx.Items.RemoveAt(itemIndex); } } //ListBox2内にドラッグされた時 private void ListBox2_DragEnter(object sender, DragEventArgs e) { //ドラッグされているデータがstring型か調べ、 //そうであればドロップ効果をMoveにする if (e.Data.GetDataPresent(typeof(string))) e.Effect = DragDropEffects.Move; else //string型でなければ受け入れない e.Effect = DragDropEffects.None; } //ListBox2にドロップされたとき private void ListBox2_DragDrop(object sender, DragEventArgs e) { //ドロップされたデータがstring型か調べる if (e.Data.GetDataPresent(typeof(string))) { ListBox target = (ListBox) sender; //ドロップされたデータ(string型)を取得 string itemText = (string) e.Data.GetData(typeof(string)); //ドロップされたデータをリストボックスに追加する target.Items.Add(itemText); } }
上記のコードではドラッグするデータとしてString型を使用しました。しかし、別プロセスのアプリケーションにドロップするのでなければ、どのような型のデータであってもかまいません。(別プロセスのアプリケーションにドロップする場合は、Control.DoDragDrop メソッドによると、「基本マネージ クラス (String 、 Bitmap 、または Metafile)、あるいは ISerializable または IDataObject を実装するオブジェクトのいずれかである必要があります」とのことです。)
上記のコードではドラッグ&ドロップ処理に必要な最低限のことしか行っていません。これを改良し、もう少しまともなコードにしていきましょう。
まず、MouseDownイベントですぐにドラッグ処理を開始するのは問題です。これでは、その後発生するはずのClickイベントが発生しません。本来ならばマウスボタンが押され、マウスがある距離以上動いたところではじめてドラッグされるべきです。この「距離」はSystemInformation.DragSizeプロパティで取得可能ですので、これを使ってMouseMoveイベントハンドラでドラッグを開始すべきか調べればよい訳です。
また、Ctrlキーを押しながらのドラッグでドロップ効果をCopyにしたいものです。そのためには、ListBox2のDragEnterイベントハンドラでe.KeyStateによりCtrlキーが押されているか調べ、押されていればe.EffectをDragDropEffects.Copyにします。
しかし、DragEnterイベントハンドラでドロップ効果の指定を行うと、ListBox2内にドラッグされてからCtrlキーを押してもドロップ効果は変化しません。このようなことのないように、ドロップ効果の指定をDragOverイベントハンドラで行うように変更します。(下のコードではさらに、Altキーを押しながらのドラッグで、ドロップ効果がLinkになるようにしています。そのため、DoDragDropメソッドでドラッグを開始する時、DragDropEffects.Allに加えてDragDropEffects.Linkも指定しています。)
さらに、ドラッグ中にマウスの右ボタンを押すことにより、ドラッグがキャンセルできるようにしましょう。ドラッグのキャンセルは、QueryContinueDragイベントハンドラで行います。e.KeyStateでマウスの右ボタンが押されているか調べ、押されていればe.ActionにDragAction.Cancelを指定して、ドラッグ操作をキャンセルします。(Escキーによるキャンセルはデフォルトで行われます。)
ついでに、ドラッグ中のマウスカーソルの形状も自分で用意したものに変更してみましょう。ドラッグ中のマウスカーソルの変更は、GiveFeedbackイベントハンドラで行います。e.UseDefaultCursorsをFalseにし、Cursor.Currentにカーソルを指定します。(下の例では、マウスポインタがListBox1やフォームにあるときには、ここで指定したカーソルになっていないかもしれません。これは、ListBox1やフォームのAllowDropプロパティがFalseになっているためです。この点を考慮すると、"noneCursor"は使わずに、e.UseDefaultCursorsをTrueにして既定のカーソルにしたほうが良いかもしれません。)
以上の機能を付け加えて書き直したコードが下記のコードです。なおここでは4つのカーソルファイル"none.cur"、"move.cur"、"copy.cur"、"link.cur"がアプリケーションと同じフォルダに存在しているものとします。"none.cur"はドロップできないときのカーソルで、"move.cur"はドロップ効果がMoveのとき、"copy.cur"はCopyのとき、"link.cur"はLinkのときのカーソルです。
'マウスの押された位置 Private mouseDownPoint As Point = Point.Empty 'アイコン Private noneCursor As New Cursor("none.cur") Private moveCursor As New Cursor("move.cur") Private copyCursor As New Cursor("copy.cur") Private linkCursor As New Cursor("link.cur") 'フォームのLoadイベントハンドラ Private Sub Form1_Load(ByVal sender As Object, _ ByVal e As EventArgs) Handles MyBase.Load 'ひとつのアイテムしか選択できないようにする ListBox1.SelectionMode = SelectionMode.One ListBox2.SelectionMode = SelectionMode.One 'ListBox1にアイテムを追加 Dim i As Integer For i = 0 To 4 ListBox1.Items.Add(i.ToString()) Next i 'ListBox2にアイテムを追加 For i = 5 To 9 ListBox2.Items.Add(i.ToString()) Next i 'ListBox2へのドロップを受け入れる ListBox2.AllowDrop = True 'ListBox1のイベントハンドラを追加 AddHandler ListBox1.MouseDown, AddressOf ListBox1_MouseDown AddHandler ListBox1.MouseMove, AddressOf ListBox1_MouseMove AddHandler ListBox1.MouseUp, AddressOf ListBox1_MouseUp AddHandler ListBox1.QueryContinueDrag, AddressOf ListBox1_QueryContinueDrag AddHandler ListBox1.GiveFeedback, AddressOf ListBox1_GiveFeedback 'ListBox2のイベントハンドラを追加 AddHandler ListBox2.DragOver, AddressOf ListBox2_DragOver AddHandler ListBox2.DragDrop, AddressOf ListBox2_DragDrop End Sub 'ListBox1でマウスボタンが押された時 Private Sub ListBox1_MouseDown( _ ByVal sender As Object, ByVal e As MouseEventArgs) 'マウスの左ボタンだけが押されている時のみドラッグできるようにする If e.Button = MouseButtons.Left Then 'ドラッグの準備 Dim lbx As ListBox = CType(sender, ListBox) 'マウスの押された位置を記憶 If lbx.IndexFromPoint(e.X, e.Y) >= 0 Then mouseDownPoint = New Point(e.X, e.Y) End If Else mouseDownPoint = Point.Empty End If End Sub Private Sub ListBox1_MouseUp( _ ByVal sender As Object, ByVal e As MouseEventArgs) mouseDownPoint = Point.Empty End Sub Private Sub ListBox1_MouseMove( _ ByVal sender As Object, ByVal e As MouseEventArgs) If mouseDownPoint.IsEmpty = False Then 'ドラッグとしないマウスの移動範囲を取得する Dim moveRect As New Rectangle( _ mouseDownPoint.X - SystemInformation.DragSize.Width / 2, _ mouseDownPoint.Y - SystemInformation.DragSize.Height / 2, _ SystemInformation.DragSize.Width, _ SystemInformation.DragSize.Height) 'ドラッグとする移動範囲を超えたか調べる If Not moveRect.Contains(e.X, e.Y) Then 'ドラッグの準備 Dim lbx As ListBox = CType(sender, ListBox) 'ドラッグするアイテムのインデックスを取得する Dim itemIndex As Integer = _ lbx.IndexFromPoint(mouseDownPoint) If itemIndex < 0 Then Return End If 'ドラッグするアイテムの内容を取得する Dim itemText As String = CStr(lbx.Items(itemIndex)) 'ドラッグ&ドロップ処理を開始する Dim dde As DragDropEffects = _ lbx.DoDragDrop(itemText, _ DragDropEffects.All Or DragDropEffects.Link) 'ドロップ効果がMoveの時はもとのアイテムを削除する If dde = DragDropEffects.Move Then lbx.Items.RemoveAt(itemIndex) End If mouseDownPoint = Point.Empty End If End If End Sub 'マウスポインタを指定する Private Sub ListBox1_GiveFeedback( _ ByVal sender As Object, ByVal e As GiveFeedbackEventArgs) '既定のカーソルを使用しない e.UseDefaultCursors = False 'ドロップ効果にあわせてカーソルを指定する If (e.Effect And DragDropEffects.Move) = _ DragDropEffects.Move Then Cursor.Current = moveCursor Else If (e.Effect And DragDropEffects.Copy) = _ DragDropEffects.Copy Then Cursor.Current = copyCursor Else If (e.Effect And DragDropEffects.Link) = _ DragDropEffects.Link Then Cursor.Current = linkCursor Else Cursor.Current = noneCursor End If End Sub 'ドラッグをキャンセルする Private Sub ListBox1_QueryContinueDrag( _ ByVal sender As Object, _ ByVal e As QueryContinueDragEventArgs) 'マウスの右ボタンが押されていればドラッグをキャンセル '"2"はマウスの右ボタンを表す If (e.KeyState And 2) = 2 Then e.Action = DragAction.Cancel End If End Sub Private Sub ListBox2_DragOver( _ ByVal sender As Object, ByVal e As DragEventArgs) 'ドラッグされているデータがstring型か調べる If e.Data.GetDataPresent(GetType(String)) Then If (e.KeyState And 8) = 8 And _ (e.AllowedEffect And DragDropEffects.Copy) = _ DragDropEffects.Copy Then 'Ctrlキーが押されていればCopy '"8"はCtrlキーを表す e.Effect = DragDropEffects.Copy ElseIf (e.KeyState And 32) = 32 And _ (e.AllowedEffect And DragDropEffects.Link) = _ DragDropEffects.Link Then 'Altキーが押されていればLink '"32"はAltキーを表す e.Effect = DragDropEffects.Link ElseIf (e.AllowedEffect And DragDropEffects.Move) = _ DragDropEffects.Move Then '何も押されていなければMove e.Effect = DragDropEffects.Move Else e.Effect = DragDropEffects.None End If Else 'string型でなければ受け入れない e.Effect = DragDropEffects.None End If End Sub 'ListBox2にドロップされたとき Private Sub ListBox2_DragDrop( _ ByVal sender As Object, ByVal e As DragEventArgs) 'ドロップされたデータがstring型か調べる If e.Data.GetDataPresent(GetType(String)) Then Dim target As ListBox = CType(sender, ListBox) 'ドロップされたデータ(string型)を取得 Dim itemText As String = CStr(e.Data.GetData(GetType(String))) If (e.Effect And DragDropEffects.Link) = _ DragDropEffects.Link Then itemText = "[Link]" + itemText End If 'ドロップされたデータをリストボックスに追加する target.Items.Add(itemText) End If End Sub
//マウスの押された位置 private Point mouseDownPoint = Point.Empty; //アイコン private Cursor noneCursor = new Cursor("none.cur"); private Cursor moveCursor = new Cursor("move.cur"); private Cursor copyCursor = new Cursor("copy.cur"); private Cursor linkCursor = new Cursor("link.cur"); //フォームのLoadイベントハンドラ private void Form1_Load(object sender, System.EventArgs e) { //ひとつのアイテムしか選択できないようにする ListBox1.SelectionMode = SelectionMode.One; ListBox2.SelectionMode = SelectionMode.One; //ListBox1にアイテムを追加 for (int i = 0; i < 5; i++) ListBox1.Items.Add(i.ToString()); //ListBox2にアイテムを追加 for (int i = 5; i < 10; i++) ListBox2.Items.Add(i.ToString()); //ListBox2へのドロップを受け入れる ListBox2.AllowDrop = true; //ListBox1のイベントハンドラを追加 ListBox1.MouseDown += new MouseEventHandler(ListBox1_MouseDown); ListBox1.MouseMove += new MouseEventHandler(ListBox1_MouseMove); ListBox1.MouseUp += new MouseEventHandler(ListBox1_MouseUp); ListBox1.QueryContinueDrag += new QueryContinueDragEventHandler(ListBox1_QueryContinueDrag); ListBox1.GiveFeedback += new GiveFeedbackEventHandler(ListBox1_GiveFeedback); //ListBox2のイベントハンドラを追加 ListBox2.DragOver += new DragEventHandler(ListBox2_DragOver); ListBox2.DragDrop += new DragEventHandler(ListBox2_DragDrop); } //ListBox1でマウスボタンが押された時 private void ListBox1_MouseDown(object sender, MouseEventArgs e) { //マウスの左ボタンだけが押されている時のみドラッグできるようにする if (e.Button == MouseButtons.Left) { //ドラッグの準備 ListBox lbx = (ListBox) sender; //マウスの押された位置を記憶 if (lbx.IndexFromPoint(e.X, e.Y) >= 0) mouseDownPoint = new Point(e.X, e.Y); } else mouseDownPoint = Point.Empty; } private void ListBox1_MouseUp(object sender, MouseEventArgs e) { mouseDownPoint = Point.Empty; } private void ListBox1_MouseMove(object sender, MouseEventArgs e) { if (mouseDownPoint != Point.Empty) { //ドラッグとしないマウスの移動範囲を取得する Rectangle moveRect = new Rectangle( mouseDownPoint.X - SystemInformation.DragSize.Width / 2, mouseDownPoint.Y - SystemInformation.DragSize.Height / 2, SystemInformation.DragSize.Width, SystemInformation.DragSize.Height); //ドラッグとする移動範囲を超えたか調べる if (!moveRect.Contains(e.X, e.Y)) { //ドラッグの準備 ListBox lbx = (ListBox) sender; //ドラッグするアイテムのインデックスを取得する int itemIndex = lbx.IndexFromPoint(mouseDownPoint); if (itemIndex < 0) return; //ドラッグするアイテムの内容を取得する string itemText = (string) lbx.Items[itemIndex]; //ドラッグ&ドロップ処理を開始する DragDropEffects dde = lbx.DoDragDrop(itemText, DragDropEffects.All | DragDropEffects.Link); //ドロップ効果がMoveの時はもとのアイテムを削除する if (dde == DragDropEffects.Move) lbx.Items.RemoveAt(itemIndex); mouseDownPoint = Point.Empty; } } } //マウスポインタを指定する private void ListBox1_GiveFeedback( object sender, GiveFeedbackEventArgs e) { //既定のカーソルを使用しない e.UseDefaultCursors = false; //ドロップ効果にあわせてカーソルを指定する if ((e.Effect & DragDropEffects.Move) == DragDropEffects.Move) Cursor.Current = moveCursor; else if ((e.Effect & DragDropEffects.Copy) == DragDropEffects.Copy) Cursor.Current = copyCursor; else if ((e.Effect & DragDropEffects.Link) == DragDropEffects.Link) Cursor.Current = linkCursor; else Cursor.Current = noneCursor; } //ドラッグをキャンセルする private void ListBox1_QueryContinueDrag( object sender, QueryContinueDragEventArgs e) { //マウスの右ボタンが押されていればドラッグをキャンセル //"2"はマウスの右ボタンを表す if ((e.KeyState & 2) == 2) e.Action = DragAction.Cancel; } private void ListBox2_DragOver(object sender, DragEventArgs e) { //ドラッグされているデータがstring型か調べる if (e.Data.GetDataPresent(typeof(string))) { //Ctrlキーが押されていればCopy //"8"はCtrlキーを表す if ((e.KeyState & 8) == 8 && (e.AllowedEffect & DragDropEffects.Copy) == DragDropEffects.Copy) e.Effect = DragDropEffects.Copy; //Altキーが押されていればLink //"32"はAltキーを表す else if ((e.KeyState & 32) == 32 && (e.AllowedEffect & DragDropEffects.Link) == DragDropEffects.Link) e.Effect = DragDropEffects.Link; //何も押されていなければMove else if ((e.AllowedEffect & DragDropEffects.Move) == DragDropEffects.Move) e.Effect = DragDropEffects.Move; else e.Effect = DragDropEffects.None; } else //string型でなければ受け入れない e.Effect = DragDropEffects.None; } //ListBox2にドロップされたとき private void ListBox2_DragDrop(object sender, DragEventArgs e) { //ドロップされたデータがstring型か調べる if (e.Data.GetDataPresent(typeof(string))) { ListBox target = (ListBox) sender; //ドロップされたデータ(string型)を取得 string itemText = (string) e.Data.GetData(typeof(string)); if ((e.Effect & DragDropEffects.Link) == DragDropEffects.Link) itemText = "[Link]" + itemText; //ドロップされたデータをリストボックスに追加する target.Items.Add(itemText); } }
補足:
さて、上記のコード(あるいはControl.DoDragDrop メソッド)をご覧になった方は、このコードを複数のアイテムが選択されたリストボックスに応用したいと考えるかもしれません。ところがこれがなかなか大変なのです(.NET Framework 1.1現在)。というのは、実際にやってみると分かりますが、なぜかリストボックスのSelectedIndices、SelectedItemsプロパティなどが正常に取得できないのです。リストボックス間のドラッグ&ドロップのサンプルコードは多く見ますが、そのどれもが複数のアイテムのドラッグ&ドロップに対応していないのはこのためです。(少なくとも私はそのようなサンプルを見たことがありませんし、それどころか、このことに触れられてもいません。)
この問題の解決法に関しては、ニュースグループ「microsoft.public.dotnet.languages.csharp」の「RE: ListBox.SelectedItems and DragDrop(From:Lion Shi)」(リンク切れ)の投稿が参考になります。
これによると解決法は、フォームのActivatedイベントハンドラで一回だけ
ListBox1.Focus() SendKeys.Send("{LEFT}")
ListBox1.Focus(); SendKeys.Send("{LEFT}");
を行うとのことです。私がやってみたところではこれだけではダメで、DoDragDropメソッド終了後ごとに同じことをしなければいけないようです。
しかしこれが解決になっているのか疑問です。ListBoxの使用はあきらめて、ListViewを使った方が良いかもしれません。