【VB.NET】DataGridViewでセルコピー&ペースト

DataGridViewでセルのコピー＆ペースト。
Excelの動きに近づくように四苦八苦・・・
・コピー元から複数行へのコピー
・複数行から複数行へのコピー
が出来るようになっています

●フォームロード

    Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        ''テーブルを作成
        Dim dataSet1 As DataSet = New DataSet("商品マスター")
        Dim dataTable1 As DataTable = dataSet1.Tables.Add("商品テーブル")
        Dim dataClumn1 As DataColumn = dataTable1.Columns.Add("ID")
        Dim dataClumn2 As DataColumn = dataTable1.Columns.Add("商品")
        Dim dataClumn3 As DataColumn = dataTable1.Columns.Add("値段")
        Dim dataClumn4 As DataColumn = dataTable1.Columns.Add("個数")

        ''テーブルにデータを追加
        dataTable1.Rows.Add(New Object() {1, "みかん", 100, "3個"})
        dataTable1.Rows.Add(New Object() {2, "パイナップル", 300, "3個"})
        dataTable1.Rows.Add(New Object() {3, "バナナ", 120, "1房"})
        dataTable1.Rows.Add(New Object() {"", "", "", ""})
        dataTable1.Rows.Add(New Object() {"", "", "", ""})
        dataTable1.Rows.Add(New Object() {"", "", "", ""})

        ''データグリッドの行の追加と削除、データ編集を不許可にする
        dataTable1.DefaultView.AllowNew = False
        dataTable1.DefaultView.AllowDelete = False
        dataTable1.DefaultView.AllowEdit = True

        ''データグリッドにテーブルを表示する
        ''（データソースにDataViewを使う）

        'DataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect
        DataGridView1.DataSource = dataTable1

    End Sub


●各キー動作

    ''' <summary>
    ''' 選択行コピー・削除・切り取り
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub DataGridView1_KeyDown1(sender As Object, e As KeyEventArgs) Handles DataGridView1.KeyDown

        If e.Control And e.KeyCode = Keys.C Then
            ''*** Ctrl+C 選択行コピー***
            copyRow()

        ElseIf e.KeyCode = Keys.Delete Then
            ''*** Delete 選択行削除***
            deleteRow()

        ElseIf e.Control And e.KeyCode = Keys.X Then
            ''*** Ctrl+X 選択行切り取り***
            copyRow()
            deleteRow()

        ElseIf e.Control And e.KeyCode = Keys.V Then
            ''*** Ctrl+V 選択行ペースト***
            CopyPaste(sender, e)
        End If

    End Sub

●コピー

    ''' <summary>
    ''' コピー
    ''' </summary>
    Private Sub copyRow()
        DataGridView1.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableWithoutHeaderText

        If DataGridView1.GetClipboardContent() IsNot Nothing Then
            Clipboard.SetDataObject(DataGridView1.GetClipboardContent())
        Else
            ''何もコピーしない
            Return
        End If

    End Sub

●削除

    ''' <summary>
    ''' 削除
    ''' </summary>
    Private Sub deleteRow()
        For Each c As DataGridViewCell In DataGridView1.SelectedCells

            Dim iR As Integer = c.RowIndex
            Dim iC As Integer = c.ColumnIndex

            If 4 <= c.ColumnIndex Then
                ''ヘッダ列以外
                DataGridView1.Rows(iR).Cells(iC).Value = ""
            End If

        Next
    End Sub

●ペースト
DataGridViewSelectionMode.FullRowSelectでないとDataGridView.SelectedRowsが取得できないようなので、苦肉の策でSelectedCellsから選択行のインデックスを取得しています(あんまりイケてないかも・・・)

    ''' <summary>
    ''' ペースト
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub CopyPaste(sender As Object, e As KeyEventArgs)

        Try

            If DataGridView1.CurrentCell Is Nothing Then
                Return
            End If

            Dim dt As DataTable = CType(DataGridView1.DataSource, DataTable)
            Dim RowIndexList(dt.Rows.Count * dt.Columns.Count) As String
            Dim ColIndexList(dt.Rows.Count * dt.Columns.Count) As String

            Dim insertRowIndexMin As Integer = dt.Rows.Count    ''選択行の最小インデックス
            Dim insertRowIndexMax As Integer = 0                ''選択行の最大インデックス
            Dim insertColIndexMin As Integer = dt.Columns.Count ''選択列の最小インデックス
            Dim insertColIndexMax As Integer = 0                ''選択列の最大インデックス

            For Each c As DataGridViewCell In DataGridView1.SelectedCells
                ''選択行列のインデックスに入れ替えていく
                If insertRowIndexMin > c.RowIndex Then insertRowIndexMin = c.RowIndex
                If insertRowIndexMax <= c.RowIndex Then insertRowIndexMax = c.RowIndex
                If insertColIndexMin > c.ColumnIndex Then insertColIndexMin = c.ColumnIndex
                If insertColIndexMax <= c.ColumnIndex Then insertColIndexMax = c.ColumnIndex
            Next

            Dim selectRowNum As Integer = (insertRowIndexMax - insertRowIndexMin) + 1 ''選択行数
            Dim selectColNum As Integer = (insertColIndexMax - insertColIndexMin) + 1 ''選択列数

            'クリップボードの内容を取得して、行で分ける
            Dim pasteText As String = Clipboard.GetText()
            If pasteText Is Nothing Then
                Return
            End If
            Dim lines As String() = getLine(pasteText)

            'タブで分割
            Dim ValColNum As Integer = lines(0).Split(ControlChars.Tab).Length ''コピー列数
            Dim ValRowNum As Integer = lines.Length ''コピー行数

            Dim isHeader As Boolean = True
            Dim num As Integer = 0
            Dim num2 As Integer = 0


            For i As Integer = insertRowIndexMin To insertRowIndexMax

                If num = 0 Or (num Mod ValRowNum = 0 And selectRowNum Mod ValRowNum = 0) Then

                    If ValColNum < selectColNum And selectRowNum Mod ValRowNum = 0 Then
                        ''コピー列数より選択列数が多いときのみ
                        For j As Integer = insertColIndexMin To insertColIndexMax

                            If num2 = 0 Or (num2 Mod ValColNum = 0 And selectColNum Mod ValColNum = 0) Then
                                setRow(lines, i, j, dt)
                            End If
                            num2 = num2 + 1

                        Next j

                    Else
                        setRow(lines, i, insertColIndexMin, dt)
                    End If

                End If

                num = num + 1

            Next i

        Catch ex As System.IO.FileNotFoundException
            Debug.Print("コピーに失敗しました")

            Return
        End Try

    End Sub

●1行ごとに各セルの値を設定

    ''' <summary>
    ''' 1行ごとに各セルの値を設定
    ''' </summary>
    ''' <param name="lines"></param>
    ''' <param name="insertRowIndex"></param>
    ''' <param name="insertColIndexMin"></param>
    ''' <param name="dt"></param>
    Public Sub setRow(lines, insertRowIndex, insertColIndexMin, dt)

        Dim rowIndex As Integer = insertRowIndex

        For Each line As String In lines
            'タブで分割
            Dim vals As String() = line.Split(ControlChars.Tab)

            '各セルの値を設定
            setCol(rowIndex, insertColIndexMin, vals, dt)


            '次の行へ
            rowIndex += 1

            ''行が無ければ終了
            If rowIndex >= dt.Rows.Count Then
                Return
            End If

            'End If
        Next line
    End Sub


●クリップボードの内容を取得して、行で分ける

    ''' <summary>
    ''' クリップボードの内容を取得して、行で分ける
    ''' </summary>
    ''' <returns></returns>
    Public Function getLine(pasteText)

        Dim lines As String()

        pasteText = pasteText.Replace(vbCrLf, vbLf)
        pasteText = pasteText.Replace(vbCr, vbLf)
        pasteText = pasteText.TrimEnd(New Char() {vbLf})
        lines = pasteText.Split(vbLf)

        Return lines

    End Function

●各セルの値を設定

    ''' <summary>
    ''' 各セルの値を設定
    ''' </summary>
    ''' <param name="rowIndex"></param>
    ''' <param name="insertColIndexMin"></param>
    ''' <param name="vals"></param>
    ''' <param name="dt"></param>
    Public Sub setCol(rowIndex, insertColIndexMin, vals, dt)

        Dim row As DataGridViewRow = DataGridView1.Rows(rowIndex)
        Dim i As Integer
        Dim colIndex As Integer = insertColIndexMin

        For i = 0 To vals.Length - 1

            row.Cells(colIndex).Value = vals(i)

            colIndex = colIndex + 1

            ''列が無ければ終了
            If colIndex >= dt.Columns.Count Then
                Exit For
            End If

        Next i

    End Sub

参考記事：
DataGridViewで選択されたセルをクリップボードにコピーできるようにする
https://dobon.net/vb/dotnet/datagridview/clipboardcopy.html

C# DataGridViewでの値のコピー＆ペーストの実装
https://effect.hatenablog.com/entry/2018/10/02/030816