|
-
Oct 11th, 2021, 11:57 AM
#1
Copying and Pasting in a DGV
I decided to break another thread I just created by doing a copy and paste action on a DGV. As it turns out, that's a bit fiddly, too. If I just click in a cell and press Ctrl+C, I get an internal exception about a method not being implemented. If I highlight JUST the text in the cell, the Ctrl+C works fine.
This appears to be more or less understood. Copying the whole cell is problematic, but copying the value in the cell is not. However, this is not intuitive to the user, and it means that pasting into multiple cells is not even possible without doing a bit more work.
I believe that I can handle the KeyDown event to check for Ctrl+C, then also check for Ctrl+V, and get the behavior people would expect, but people are weird, so this won't always work. People expect that a cell in a DGV is it's contents, so they expect to be able to select a cell and copy and paste it. Lots of people know Ctrl+C/Ctrl+V, but I'm always surprised at the number that right click and select Copy, then select Paste, which would certainly break the KeyDown approach. People also aren't going to understand the idea of selecting the contents of a cell rather than the whole cell, so that's kind of a non-starter to begin with.
For these reasons, the official MS means for copying and pasting in a DGV seem to be out of consideration. Before I go build my own grid to get around this (I've done it before, I'll do it again if I have to), I was wondering whether anybody has a way to do copying and pasting in a DGV that works the way people would expect it to?
My usual boring signature: Nothing
 
-
Oct 11th, 2021, 02:18 PM
#2
Re: Copying and Pasting in a DGV
same application than for the other thread :
for the copy :
Code:
copy_data_from_datagrid(DataGrid_Courbe.GetClipboardContent) ' I put this in a right click option (ContextMenuStrip)
'copu sub
Sub copy_data_from_datagrid(ByVal Data As Object)
If Not IsNothing(Data) Then
My.Computer.Clipboard.Clear()
Clipboard.SetDataObject(Data)
End If
End Sub
for the paste
Code:
'paste sub
Sub paste_clipboard_to_datagrid()
On Error Resume Next
Dim lignes() As String
Dim Cell() As String
Dim row As Integer
Dim nb_lignes As Integer
Dim nb_lignes_restante As Integer
Dim nom_courbe As String
row = DataGrid_Courbe.SelectedCells(0).RowIndex 'get the index of the selected line
lignes = Clipboard.GetData(DataFormats.Text).ToString.Split({ControlChars.Cr, ControlChars.Lf}, StringSplitOptions.RemoveEmptyEntries) 'get from clipboard an array of data to paste, one line of array for each line copied
nb_lignes = lignes.Length
nb_lignes_restante = DataGrid_Courbe.Rows.Count - row
If nb_lignes > nb_lignes_restante Then 'check if you need to create new line to paste the data
'create new lines if needed
For i = 1 To nb_lignes - nb_lignes_restante + 1
DataGrid_Courbe.Rows.Add()
Next
End If
'fill the lines
For i = 1 To nb_lignes
Dim j As Integer
Cell = lignes(i - 1).Split(vbTab)
For j = 0 To 1
DataGrid_Courbe.Rows(row + i - 1).Cells(j).Value = Cell(j)
Next
Next
' update the curve
....
End Sub
if you want I can send to you this part of the application but it will be in french.
The best friend of any programmer is a search engine 
"Don't wish it was easier, wish you were better. Don't wish for less problems, wish for more skills. Don't wish for less challenges, wish for more wisdom" (J. Rohn)
“They did not know it was impossible so they did it” (Mark Twain)
-
Oct 11th, 2021, 03:22 PM
#3
Re: Copying and Pasting in a DGV
Not yet. It's interesting to see. After thinking it over a bit, I'm not quite sure what I want to do here. I already did something that flood filled the columns, and there will always be an issue with copy/paste in this case, since each column has different constraints on it. What works in one cell won't work in another. One whole column can't be copy/pasted at all (every value had better be unique, and not just to the grid, or somebody has messed up), some columns are entwined with others, and so forth. For that reason, I'm not quite certain what I want to do with copying and pasting.
Still, that's an interesting approach. It looks like it could be either right click or Ctrl+C without much difficulty, so it would work. I might also be able to add my peculiar paste constraints into copy/paste. For example, in the column that has to be unique, I could just disable the copying. I might fiddle around with that a bit.
My usual boring signature: Nothing
 
-
Oct 11th, 2021, 04:10 PM
#4
Re: Copying and Pasting in a DGV
The DGV has an EditMode option of "EditOnEnter" that would probably solve the error.
-
Oct 11th, 2021, 06:18 PM
#5
Hyperactive Member
Re: Copying and Pasting in a DGV
This is one That is virtually but not quite finished yet (and some bits have been copied form various sources and altered), Copy paste works ok, the only issue is in deleting rows, can only delete one row at a time, even if more are selected, I am currently working to fix this. However the rest of it works well and the cut / copy paste has the same rules as excel. You can also cut / copy paste from an external source to the DGV.
There is a known bug, that if you actually select the cell and are editing the actual contents and then right click, two pop up windows appear, the one I defined and another one which looks to be a pre defined one from somewhere.
If it is defined as read only then you can only copy
Any suggested improvements or bug fixes welcome
Code:
Public Class DataGridViewCutPaste
Inherits DataGridView
Private WithEvents contextMenuDataGrid As New ContextMenuStrip
Public Sub New()
End Sub
Public Property BindingSource() As BindingSource ' this now acts as get & set to the same name prefixed with underscore _
Public Property CutPasteReadWrite() As Boolean = False ' allows alteration to the datagridview
Private Function CheckBindingsource(ByRef aBindingsource As BindingSource)
If aBindingsource Is Nothing Then
MsgBox("bindingsource not defined")
CheckBindingsource = False
Exit Function
End If
CheckBindingsource = True
End Function
Private Sub ValidateData_Click(ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
If _CutPasteReadWrite = True Then
Select Case e.KeyCode 'read write options
Case 86 'ctrl V
PasteClipboardValue()
Case 88 'ctrl X
cut()
End Select
End If
Select Case e.KeyCode ' read options
Case 67 ' ctrl c
CopyToClipboard()
End Select
'MsgBox(e.KeyCode)
End Sub
Private Sub me_CellMouseDown(sender As Object, e As System.Windows.Forms.DataGridViewCellMouseEventArgs) Handles Me.CellMouseDown
If e.Button = MouseButtons.Right And e.RowIndex > -1 Then
Me.CurrentCell = Me(e.ColumnIndex, e.RowIndex)
If contextMenuDataGrid.Items.Count = 0 Then
If _CutPasteReadWrite = True Then
contextMenuDataGrid.Items.Add("Add Row")
contextMenuDataGrid.Items.Add("Add 10 Rows")
contextMenuDataGrid.Items.Add("Insert Row at Cursor")
contextMenuDataGrid.Items.Add("Delete Row")
contextMenuDataGrid.Items.Add("Paste")
contextMenuDataGrid.Items.Add("Cut")
End If
contextMenuDataGrid.Items.Add("Copy")
End If
contextMenuDataGrid.Show(Me, e.Location)
End If
End Sub
Private Sub contextMenuDataGrid_Click(sender As Object, ByVal e As System.Windows.Forms.ToolStripItemClickedEventArgs) Handles contextMenuDataGrid.ItemClicked
contextMenuDataGrid.Hide()
If CheckBindingsource(_BindingSource) = False Then Exit Sub
Select Case e.ClickedItem.Text
Case "Copy"
CopyToClipboard()
Case "Cut"
cut()
Case "Paste"
PasteClipboardValue()
Case "Insert Row at Cursor"
insertRowAtCursor()
Case "Add Row"
_BindingSource.AddNew()
Case "Add 10 Rows"
Add10Rows()
Case "Delete Row"
DeleteRow()
End Select
End Sub
Public Sub CopyToClipboard() ' public so it can be called off the toolstrip on the master form
Dim dataObj As DataObject = Me.GetClipboardContent
If Not IsNothing(dataObj) Then
Clipboard.SetDataObject(dataObj)
End If
End Sub
Public Sub cut() ' public so it can be called off the toolstrip on the master form
CopyToClipboard()
For counter As Integer = 0 To Me.SelectedCells.Count - 1
Me.SelectedCells(counter).Value = String.Empty
Next
End Sub
Sub DeleteRow()
Dim SelectedRowCount As Integer = Me.SelectedCells.OfType(Of DataGridViewCell)().Select(Function(x) x.RowIndex).Distinct().Count()
If CheckBindingsource(_BindingSource) = False Then Exit Sub
_BindingSource.RemoveCurrent()
End Sub
Sub Add10Rows()
Dim startingCell As Integer = Me.CurrentCell.ColumnIndex
If CheckBindingsource(_BindingSource) = False Then Exit Sub
For i = 1 To 10
_BindingSource.AddNew()
Next
Me.CurrentCell = Me.Rows(0).Cells(startingCell)
End Sub
Sub insertRowAtCursor()
If CheckBindingsource(_BindingSource) = False Then Exit Sub
Dim DataGridViewCursorCurrentRow As Integer = Me.CurrentRow.Index
Dim startingCell As Integer = Me.CurrentCell.ColumnIndex
_BindingSource.AddNew()
Dim dataGridROwCount As Integer = Me.RowCount
Dim j As Integer
For j = 0 To Me.Columns.Count - 1
For i = dataGridROwCount - 1 To DataGridViewCursorCurrentRow + 1 Step -1
Me.Rows(i).Cells(j).Value = Me.Rows(i - 1).Cells(j).Value
Me.Rows(i - 1).Cells(j).Value = ""
Next
Next
Me.CurrentCell = Me.Rows(0).Cells(startingCell)
End Sub
Private Sub PasteClipboardValue()
If Me.SelectedCells.Count = 0 Then
MessageBox.Show("No Cell selected", "Paste")
Exit Sub
End If
Dim StartingCell As DataGridViewCell = GetStartingCell(Me)
Dim SelectedrowCount = Me.SelectedCells.OfType(Of DataGridViewCell)().Select(Function(x) x.RowIndex).Distinct().Count()
Dim SelectedColumnCount = Me.SelectedCells.OfType(Of DataGridViewCell)().Select(Function(x) x.ColumnIndex).Distinct().Count()
Dim startingCellRow = StartingCell.RowIndex
Dim StartingCellColumn = StartingCell.ColumnIndex
Dim clipBoardValue As Dictionary(Of Integer, Dictionary(Of Integer, String)) = ClipboardValues(Clipboard.GetText.TrimEnd(vbCr, vbLf)) 'trim end needed due to excel
Dim clipboardRowCount As Integer = clipBoardValue.Keys.Count
Dim clipboardArray(1000, 30) As String
Dim i As Integer = 0
Dim j As Integer = 0
Dim maxArrayCol As Integer = 0
Dim maxarrayrow As Integer = 0
For Each rowkey As Integer In clipBoardValue.Keys
Dim startingCellColumnIndex As Integer = StartingCell.ColumnIndex
For Each cellkey As Integer In clipBoardValue(rowkey).Keys
Dim cell As DataGridViewCell = Me(startingCellColumnIndex, startingCellRow)
cell.Value = clipBoardValue(rowkey)(cellkey)
'If cell.Value Is Nothing Then
clipboardArray(i, j) = vbNull
' Else
clipboardArray(i, j) = cell.Value.trim(vbCr, vbLf)
' End If
j += 1 'j = j + 1
Next
If j > maxArrayCol Then maxArrayCol = j
j = 0
i += 1 'i = i + 1
Next
maxarrayrow = i
Dim maxClipboardCol As Integer = maxArrayCol
'clipboadarray Contains the clipboard with maxcolrow & col giving the number of rows & colums subtract 1 to get array
Dim cj As Integer = 0
Dim cc As Integer = 0
If SelectedrowCount = 1 And SelectedColumnCount = 1 Then ' this is if only a starting postion has been specified
Dim rowsLefttotheBottom = Me.RowCount - startingCellRow
Dim columnsLefttotheRight = Me.ColumnCount - StartingCellColumn
If rowsLefttotheBottom >= maxarrayrow Then SelectedrowCount = maxarrayrow Else SelectedrowCount = SelectedrowCount + rowsLefttotheBottom - 1
If columnsLefttotheRight >= maxArrayCol Then SelectedColumnCount = maxArrayCol Else SelectedColumnCount = SelectedColumnCount + columnsLefttotheRight - 1
End If
Dim rowsFilled As Integer
Dim rowsFilledMultiple = False
Dim columnsFilledMultiple = False
If SelectedrowCount Mod maxarrayrow = 0 Then rowsFilledMultiple = True Else rowsFilledMultiple = False
If SelectedColumnCount Mod maxArrayCol = 0 Then columnsFilledMultiple = True Else columnsFilledMultiple = False
For i = startingCellRow To startingCellRow + SelectedrowCount - 1
For j = StartingCellColumn To StartingCellColumn + SelectedColumnCount - 1
Dim cell As DataGridViewCell = Me(j, i)
cell.Value = clipboardArray(cj, cc)
Application.DoEvents()
cc += 1 'cc = cc + 1
Dim numberOfColsLeft = StartingCellColumn + SelectedColumnCount - 1
If cc >= maxClipboardCol Then
If cc >= maxClipboardCol Then
cc = 0
If columnsFilledMultiple = False Then Exit For
End If
End If
Next
rowsFilled += 1
cc = 0
cj += 1 'cj = cj + 1
If cj >= clipboardRowCount Then
cj = 0
If rowsFilledMultiple = False Then Exit For
End If
Next
End Sub
Private Function GetStartingCell(dgView As DataGridView) As DataGridViewCell
If dgView.SelectedCells.Count = 0 Then Return Nothing
Dim rowIndex As Integer = dgView.Rows.Count - 1
Dim ColIndex As Integer = dgView.Columns.Count - 1
For Each dgvcell As DataGridViewCell In dgView.SelectedCells
If dgvcell.RowIndex < rowIndex Then rowIndex = dgvcell.RowIndex
If dgvcell.ColumnIndex < ColIndex Then ColIndex = dgvcell.ColumnIndex
Next
Return dgView(ColIndex, rowIndex)
End Function
Private Function ClipboardValues(clipboardvalue As String) As Dictionary(Of Integer, Dictionary(Of Integer, String))
Dim lines() As String = clipboardvalue.Split(CChar(Environment.NewLine))
Dim copyValues As Dictionary(Of Integer, Dictionary(Of Integer, String)) = New Dictionary(Of Integer, Dictionary(Of Integer, String))
For i As Integer = 0 To lines.Length - 1
copyValues.Item(i) = New Dictionary(Of Integer, String)
Dim linecontent() As String = lines(i).Split(ChrW(Keys.Tab))
If linecontent.Length = 0 Then
copyValues(i)(0) = String.Empty
Else
For j As Integer = 0 To linecontent.Length - 1
copyValues(i)(j) = linecontent(j)
Next
End If
Next
Return copyValues
End Function
End Class
Last edited by Signalman; Oct 11th, 2021 at 06:35 PM.
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|