2013年2月26日火曜日

[VB.NET]DataGridViewでのドラッグアンドドロップ

以下のサイトにあったC#のソースコードをVB.NETに修正&一部変更しました。
http://social.msdn.microsoft.com/forums/ja-JP/csharpgeneralja/thread/e59a4043-6298-4653-809f-5c8fcf04f2e6

変更点として、
行セレクトモード⇒セルセレクトモードにして、
特定のカラム(ここではColumn0のImage)上でしかドラッグアンドドロップが起動しないようにしています。




Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load


        'テストの準備
        'データグリッド
        Dim objTable As New DataTable
        Dim objDR As DataRow
        objTable.Columns.Add(New DataColumn("項目1", GetType(String)))
        objTable.Columns.Add(New DataColumn("項目2", GetType(Integer)))
        For i As Integer = 0 To 10
            objDR = objTable.NewRow
            objDR("項目1") = i.ToString & i.ToString & i.ToString & i.ToString
            objDR("項目2") = i
            objTable.Rows.Add(objDR)
        Next
        Me.DataGridViewUe.DataSource = objTable


        For Each col As DataGridViewColumn In Me.DataGridViewUe.Columns
            col.SortMode = DataGridViewColumnSortMode.NotSortable
        Next
        Me.DataGridViewUe.AllowDrop = True

    End Sub

Private Sub DataGridViewUe_MouseDown(sender As System.Object, e As MouseEventArgs) Handles DataGridViewUe.MouseDown

        OwnBeginGrabRowIndex = -1

        If (e.Button <> MouseButtons.Left And MouseButtons.Left <> MouseButtons.Left) Then Return


        Dim hit As DataGridView.HitTestInfo = DataGridViewUe.HitTest(e.X, e.Y)

    If hit.ColumnIndex <> 0 Then Return
        If (hit.Type <> DataGridViewHitTestType.Cell) Then Return


        'ドラッグ&ドロップの開始
        'クリック時などは -1 に戻らないが問題なし
        OwnBeginGrabRowIndex = hit.RowIndex


        'Me.TextBox1.Text = OwnBeginGrabRowIndex
        'Me.TextBox2.Text = "すたーと"
        DataGridViewUe.DoDragDrop(OwnBeginGrabRowIndex, DragDropEffects.Move)



    End Sub
  
 
    Private Sub DataGridViewUe_DragOver(sender As System.Object, e As DragEventArgs) Handles DataGridViewUe.DragOver


        'Integer型の場合は、エフェクトをMoveに変更(ここはカラムによるのかな?)
        If e.Data.GetDataPresent(GetType(Integer)) Then
            e.Effect = DragDropEffects.Move
        Else
            e.Effect = DragDropEffects.None
        End If

        Me.DataGridViewUe.Rows(OwnBeginGrabRowIndex).Selected = True


        Dim kara, made As Integer
        Dim tugi As Boolean
        Dim valid As Boolean = DecideDropDestinationRowIndex(DataGridViewUe, e, kara, made, tugi)


        'ドロップ先マーカーの表示・非表示の制御
        Dim needRedraw As Boolean = (valid <> DropDestinationIsValid)
        If (valid) Then
            needRedraw = (needRedraw Or (made <> DropDestinationRowIndex) Or (tugi <> DropDestinationIsNextRow))
        End If

        If (needRedraw) Then
            If (DropDestinationIsValid) Then
                DataGridViewUe.InvalidateRow(DropDestinationRowIndex)
            End If
            If (valid) Then
                DataGridViewUe.InvalidateRow(made)
            End If
        End If

        DropDestinationIsValid = valid
        DropDestinationRowIndex = made
        DropDestinationIsNextRow = tugi

    End Sub

    Private Sub DataGridViewUe_DragLeave(sender As System.Object, e As DragEventArgs) Handles DataGridViewUe.DragLeave
        If (DropDestinationIsValid) Then
            DropDestinationIsValid = False
            DataGridViewUe.InvalidateRow(DropDestinationRowIndex)
        End If
    End Sub

    Private Sub DataGridViewUe_DragDrop(sender As System.Object, e As DragEventArgs) Handles DataGridViewUe.DragDrop

        Dim kara, made As Integer
        Dim tugi As Boolean

        If (DecideDropDestinationRowIndex(DataGridViewUe, e, kara, made, tugi) = False) Then
            Return
        End If

        DropDestinationIsValid = False

        'データの移動
        made = MoveDataValue(kara, made, tugi)

        Me.DataGridViewUe.CurrentCell = Me.DataGridViewUe(Me.DataGridViewUe.CurrentCell.ColumnIndex, made)


        Me.DataGridViewUe.Invalidate()


    End Sub


    Private Sub DataGridViewUe_RowPostPaint(sender As System.Object, e As DataGridViewRowPostPaintEventArgs) Handles DataGridViewUe.RowPostPaint

        'ドロップ先のマーカーを描画
        If (DropDestinationIsValid And e.RowIndex = DropDestinationRowIndex) Then
            Using objpen As New Pen(Color.Red, 4)
                Dim y As Integer = IIf(DropDestinationIsNextRow = False, e.RowBounds.Y + 2, e.RowBounds.Bottom - 2)
                e.Graphics.DrawLine(objpen, e.RowBounds.X, y, e.RowBounds.X + 50, y)
            End Using
        End If
    End Sub



    'ドロップ先の行の決定
    Private Function DecideDropDestinationRowIndex(grid As DataGridView, e As DragEventArgs, ByRef kara As Integer, ByRef made As Integer, ByRef tugi As Boolean) As Boolean


        'If Not e.Data.GetDataPresent(GetType(Integer)) Then Return
        kara = DirectCast(e.Data.GetData(GetType(Integer)), Integer)

        '元の行が追加用の行であれば、常に false
        If (grid.NewRowIndex <> -1 And grid.NewRowIndex = kara) Then
            made = 0
            tugi = False
            Return False
        End If



        Dim clientPoint As Point = grid.PointToClient(New Point(e.X, e.Y))
        '上下のみに着目するため、横方向は無視する
        clientPoint.X = 1
        Dim hit As DataGridView.HitTestInfo = grid.HitTest(clientPoint.X, clientPoint.Y)

        made = hit.RowIndex
        If (made = -1) Then

            Dim top As Integer = IIf(grid.ColumnHeadersVisible, grid.ColumnHeadersHeight, 0)
            top += 1  '// ...

            If (top > clientPoint.Y) Then
                'ヘッダへのドロップ時は表示中の先頭行とする
                made = grid.FirstDisplayedCell.RowIndex
            Else
                '最終行へ
                made = grid.Rows.Count - 1
            End If

        End If

        '追加用の行は無視
        If (made = grid.NewRowIndex) Then
            made -= made
        End If

        'Me.NumericTextBox1.Text = made

        tugi = (made > kara)
        Return (kara <> made)

    End Function



    'データの移動
    Private Function MoveDataValue(ByVal kara As Integer, ByVal made As Integer, ByVal tugi As Boolean) As Integer

        Dim table As DataTable = DirectCast(DataGridViewUe.DataSource, DataTable)

        '移動するデータの退避(計算列があればたぶんダメ)
        Dim rowData = table.Rows(kara).ItemArray
        Dim row As DataRow = table.NewRow()
        row.ItemArray = rowData

        '移動元から削除
        table.Rows.RemoveAt(kara)
        If (made > kara) Then made = made - 1
        '移動先へ追加
        If (tugi) Then made = made + 1

        If (made <= table.Rows.Count) Then
            table.Rows.InsertAt(row, made)
        Else
            table.Rows.Add(row)
        End If

        Return table.Rows.IndexOf(row)
    End Function



End Class





5 件のコメント:

  1. コメント失礼します。
    'OwnBeginGrabRowIndex'と'DropDestinationIsValid'と'DropDestinationIsNextRow'が何なのかお聞きしたいです。

    返信削除
  2. 失礼しました
    Dim DropDestinationIsValid AS bool
    Dim DropDestinationRowIndex AS int
    Dim DropDestinationIsNextRow As bool
    が抜けていました

    返信削除
    返信
    1. 返信ありがとうございます。
      とても参考になりました。

      削除
  3. '追加用の行は無視
    If (tugi = grid.NewRowIndex) Then
    made -= made
    End If

    なぜboolとRowIndexを比較するのですか。

    返信削除
  4. 元の環境がないので検証はできないのですが
    変換前の参考サイトを見ると
    If (made = grid.NewRowIndex) Then
    が正しそうです
    私の変換ミスだと思います
    失礼しました

    返信削除