はじめに
1つのitemをドラッグアンドドロップで並び替えるのはすぐにできたが、複数のitemを一括で移動させる方法は調べても出てこなかったのでメモ。
Shift
キーやctrl
キーで複数選択、そっから運びたい。
目指したのはエクスプローラー。
・View.detailの場合で実装してます
ItemDragイベント
ドラッグアンドドロップ処理の開始。
選択中itemをつっこむ。
private void ItemDragMulti(object sender, ItemDragEventArgs e)
{
ListView lv = (ListView)sender;
DoDragDrop(lv.SelectedItems, DragDropEffects.Move);
}
DragEnterイベント
一応、ドラッグの中身もチェック
private void DragEnterMulti(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent(typeof(SelectedListViewItemCollection)))
{
e.Effect = DragDropEffects.None;
}
e.Effect = e.AllowedEffect;
}
DragOverイベント
DragOverイベントは、コントロールの境界を越えた時に発生するみたい。
ユーザーのドラッグ操作に合わせて、InsertionMark
を表示させます。
private void DragOverMulti(object sender, DragEventArgs e)
{
Point targetPoint = PointToClient(new Point(e.X, e.Y));
// NearestIndex はドラッグ前itemとインデックスが同じ時、-1を返す
int targetIndex = InsertionMark.NearestIndex(targetPoint)
if (targetIndex > -1)
{
Rectangle itemBounds = GetItemRect(targetIndex);
// itemの中点から下なら、InsertionMarkは次のitemに表示しますよ
if (targetPoint.Y > itemBounds.Top + (itemBounds.Height / 2))
{
InsertionMark.AppearsAfterItem = true;
}
else
{
InsertionMark.AppearsAfterItem = false;
}
}
InsertionMark.Index = targetIndex;
}
DragDropイベント
いよいよ落とします。
DragEventArgs
から、ItemDragイベントでつっこんだ選択中itemたちを引っ張り出していく。
下では、挿入先インデックスがわかったらメソッドに投げています。
private void DragDropMulti(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(typeof(SelectedListViewItemCollection)))
{
var srcItems = (SelectedListViewItemCollection)e.Data.GetData(typeof(SelectedListViewItemCollection));
int destIndex = InsertionMark.Index;
// 挿入マークが非表示である
if (destIndex == -1)
{
return;
}
MoveRows(this, destIndex, srcItems);
InsertionMark.Index = -1;
}
}
MoveRowsメソッド
DragOverイベントで、マウス座標が各itemの中点より下にあるかで、InsertionMarkの位置が変わるようにしていました。
こちらの動きもそれに合わせます。
あと、複数項目の移動にforeach
で対応しています。
この時、複数項目かつ下から上への移動の時にはうまくいかず。
例えば、「5,6」番目を2番目にドラッグアンドドロップすると、「6,5」の順番で入ってしまいます。
これは5番目が先に移動し、その後の6番目の挿入も同じtargetIndex
に挿入しているからですね。
この時だけ、項目毎にtargetIndex
をインクリメントしてやればオッケーです。
private void MoveRows(ListView list, int insertIndex, SelectedListViewItemCollection col)
{
foreach (ListViewItem item in col)
{
int oldIndex = item.Index;
// AppearsAfterItem が有効なら挿入先は1つ下へ
int newIndex =
InsertionMark.AppearsAfterItem
? insertIndex + 1
: insertIndex;
list.Items.Insert(newIndex, (ListViewItem)item.Clone());
list.Items[newIndex].Selected = true;
list.Items.Remove(item);
// 下から上へは、複数項目の場合、順序が反転してしまう
if (oldIndex > insertIndex)
{
insertIndex++;
}
}
}
おわりに
複数項目のドラッグアンドドロップ、思ったよりネットに記事がなくてちょっと驚きました。(使う場面多そうだなぁと思ってたので。リサーチ力不足か...?)
ただ、今回の実装では以下と併用できず。
・仮想ビュー使用時 (たしか、選択中のitemが使えなかった気が...)
・ListViewItemSorter
使用時 (はて...?)
仮想ビューは何となくできそうですが、ListVIewItemSorter
を使ったときにドラッグアンドドロップがうまくいかなくなったのは検討もついておりません。
今回の実装に加えて、ヘッダー部クリックのソートも入れたいが...
うーん。