LoginSignup
0
0

ListViewで複数の項目をドラッグアンドドロップで並び替える

Posted at

はじめに

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を使ったときにドラッグアンドドロップがうまくいかなくなったのは検討もついておりません。
今回の実装に加えて、ヘッダー部クリックのソートも入れたいが...
うーん。

参考

方法: Windows フォーム ListView コントロールに挿入マークを表示する
Drag&Dropを行う

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0