2

I'm currently trying to copy selected rows from one DataGridView to another.
I'm trying to capture the value of the CheckBox, where if it's checked, then the entire row will be copied to another DataGridView.

For example, like add to cart then review cart. I've referred to the following post:
Copy selected datagridrow to new datagridview on different form

However it doesn't seem to help.
I've tried using a For loop like the one below, but I'm not entirely sure how to go about this.

Private Sub btnAdd_Click(sender As Object, e As EventArgs) Handles btnAdd.Click
    Dim dt As New DataTable()
    AppendColumnsToDGV2()
    For Each row As DataGridViewRow In DataGridView1.Rows
        If row.Cells("SelectColumn").Value = True Then
            Dim NewRow As DataRow
            For i As Integer = 0 To row.Cells.Count - 1
                NewRow(i) = row.Cells(i).Value
                DataGridView2.Rows.Add(NewRow)
            Next
        End If
    Next

AppendColumnsToDGV2:

  Private Sub AppendColumnsToDGV2()
      Dim dt As New DataTable
      'dt.Columns.Add(CreateDGVCheckBoxCol())
      'dt.Columns.Add(CreateImageColumn())
      dt.Columns.Add(DataGridView1.Columns(3).HeaderText)
      dt.Columns.Add(DataGridView1.Columns(4).HeaderText)
      dt.Columns.Add(DataGridView1.Columns(5).HeaderText)
      dt.Columns.Add(DataGridView1.Columns(6).HeaderText)
      DataGridView2.DataSource = dt
End Sub

What I'm doing here isn't working and I have no idea how to go about this.
Any help would be appreciated, thank you, kindly.

Whenever I run this code, I get the error:

System.NullReferenceException: Object reference not set to an instance of an object

I'm not sure how to fix it.

This is what the DataGridView looks like:

WhatDGVLooksLike

Jimi
  • 29,621
  • 8
  • 43
  • 61
  • "DataGridView2.Rows.Add(NewRow)" should follow the first Next statement. Are you getting any errors, or just not seeing anything happen? – JerryM Jan 22 '19 at 16:56
  • Also, newRow should be constructed by the DataTable. E.g. `Dim newRow = dt.NewRow`. – JerryM Jan 22 '19 at 17:00
  • @JerryM I get the error `System.NullReferenceException: 'Object reference not set to an instance of an object.'`. I'm using the datarow when a value apparently hasn't been assigned to it. – SchmellerMeller Jan 22 '19 at 17:02
  • You should pass dt to the AppendColumnsToDGV2 routine. Example `Private Sub AppendColumnsToDGV2 (dt As DataTable)'. You are currently using 2 separate datatables. – JerryM Jan 22 '19 at 17:06
  • Are you still using the classes generated from the JSON shown in your [previous question](https://stackoverflow.com/q/54204643/7444103)? If so, the solution is really simple. – Jimi Jan 22 '19 at 20:13
  • @Jimi I am yes, it's great to see you xD – SchmellerMeller Jan 22 '19 at 20:19
  • All right then, give me a minute (or two :), I'll write something down. – Jimi Jan 22 '19 at 20:23

3 Answers3

5

This question is strictly related to the previous one:
Display images in a DataGridView column using JSON objects as DataSource

You're using a sub-class (Result) of the RootObject to fill the first DataGridView.

Modify the Result class as follows:

  • Add a new property, Selected As Boolean, decorated with a <JsonIgnore> attribute.
  • Add a new sub-class, called SelectionResult here, a selection of properties of the Result class that you think are needed in the second DataGridView to show the selected products.
  • Add a copy method to the Result class which returns a sub-section of itself as a SelectionResult object.

Public Class Result
    <JsonIgnore>
    Public Property Selected As Boolean

    '(...)

    Public Function GetSelectionResult() As SelectionResult
        Return New SelectionResult With {
            .ID = Me.id,
            .Image = Me.Image,
            .Name = Me.Name,
            .ProductDescription = Me.ProductDescription,
            .Department = Me.Department,
            .Price = Me.Price,
            .Unitprice = Me.Unitprice
        }
    End Function
End Class

Public Class SelectionResult
    Public Property ID As Integer
    Public Property Image As Bitmap
    Public Property Name As String
    Public Property ProductDescription As String
    Public Property Department As String
    Public Property Price As Decimal
    Public Property Unitprice As Decimal
End Class
  • Add two List(Of Class) as Fields in the Form. The main class, in the previous question, was called ProductsQuery, so I'm re-using the names already defined there:

Private CurrentProducts As List(Of ProductsQuery.Result) = New List(Of ProductsQuery.Result)()
Private SelectedProducts As List(Of ProductsQuery.SelectionResult) = New List(Of ProductsQuery.SelectionResult)()
  • In the method that fills the first DataGridView, initialize the CurrentProducts List:

    CurrentProducts = New List(Of ProductsQuery.Result)()
    
  • After the JSON has beed deserialized, fill the List with the JSON results:

    CurrentProducts.AddRange(JsonPost.uk.ghs.Products.Results)
    

In the event handler of the Button that adds the selected products to the second DataGridView, insert this code:

Edit:
The SelectedProducts list preserves the items selected in the first DataGridView: only the items that are not already in the CurrentProducts list are added to the selection.

The btnRemoveSelection Button removes the selected items in the second DataGridView from the SelectedProducts list. The DataGridView Row selection is somewhat cumbersome, so might want to add a CheckBox Column to ease the selection of the items to remove.

Private Sub btnAdd_Click(sender As Object, e As EventArgs) Handles btnAdd.Click
    SelectedProducts.AddRange(CurrentProducts.
                       Where(Function(p) p.Selected = True AndAlso
                             (Not SelectedProducts.Any(Function(sp) sp.ID = p.id))).
                       Select(Function(p) p.GetSelectionResult()).ToArray())
    ResetCart()
End Sub

Private Sub btnRemoveSelection_Click(sender As Object, e As EventArgs) Handles btnRemoveSelection.Click
    If DataGridView2.SelectedRows.Count = 0 Then Return

    Dim itemsRemoved As Boolean = False
    Dim selectedItems() As Integer = DataGridView2.SelectedRows.
                                     OfType(Of DataGridViewRow)().
                                     Select(Function(r) CInt(r.Cells("ID").Value)).ToArray()
    For Each ID As Integer In selectedItems
        Dim currentIndex As Integer = SelectedProducts.FindIndex(Function(p) p.ID = ID)
        If currentIndex >= 0 Then
            SelectedProducts.RemoveAt(currentIndex)
            itemsRemoved = True
        End If
    Next
    If itemsRemoved Then
        ResetCart()
    End If
End Sub

Private Sub ResetCart()
    DataGridView2.DataSource = Nothing
    DataGridView2.DataSource = SelectedProducts
    DataGridView2.Columns(0).Visible = False
    DataGridView2.AutoResizeRows()
End Sub

This fills the List(Of SelectedProducs) with the selected elements of the first DataGridView and sets the DataSource of the second DataGridView to this List.

Note that the first Column of the DataGridView is set to Visible = False, because that Column corresponds to the ID property of the element selected

The GetSelectionResult() of the Result class returns the properties values that have been defined in the SelectionResult class. You can of course re-define this class to contain whatever properties you see fit.


This is the result of these modifications:

DataGridView JSON results

Jimi
  • 29,621
  • 8
  • 43
  • 61
  • Thank you for this @Jimi do you know how I would actually get the data in the second DGV to stay there. I've removed the lines `DataGridView2.DataSource = Nothing` and `SelectedProducts.Clear()` however, there's no difference. Whenever I make another search they're gone. – SchmellerMeller Jan 22 '19 at 21:56
  • Do you mean that you want the second DGV to keep the results of different selections made in different moments? As a *deposit of choices* or a shop cart? – Jimi Jan 22 '19 at 22:01
  • Yeah exactly, like a shopping cart, where all selected results remain for the end user to review when they wish. – SchmellerMeller Jan 22 '19 at 22:03
  • All right, that makes sense. I'll make an edit as soon as a I have a moment. – Jimi Jan 22 '19 at 22:06
  • Code updated. I added a second button that allows to remove items from the selection list. Read the notes. See whether it fits. – Jimi Jan 22 '19 at 22:58
  • Exactly what I had in mind, thank you very much @Jimi. – SchmellerMeller Jan 23 '19 at 08:37
2

If you are databinding correctly, your underlying data will update when the checkbox is clicked. Then you can just use some LINQ. You should avoid iterating over your DataGridViewRows whenever possible (here it is possible) because they shouldn't hold the data, rather display it.

This simple example works in a form with two DataGridViews and one Button with default names, in vb.net.

Public Class Form1

    Private allProducts As List(Of Product)
    Private basketProducts As List(Of Product)

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        allProducts = New List(Of Product) From {
                New Product() With {.Name = "Fairy Gel", .ID = 1},
                New Product() With {.Name = "Fairy Caps", .ID = 2},
                New Product() With {.Name = "Fairy Liquid", .ID = 3}}
        DataGridView1.DataSource = allProducts
    End Sub

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        basketProducts = allProducts.Where(Function(p) p.Selected).ToList()
        DataGridView2.DataSource = basketProducts
    End Sub

    ' dummy class to emulate your data
    Private Class Product
        Public Property Selected As Boolean
        Public Property Name As String
        Public Property ID As Long
    End Class

End Class

enter image description here

djv
  • 15,168
  • 7
  • 48
  • 72
  • 1
    @SchmellerMeller this requires no extensions nor additional references past those default in a WinForms application. What are you referring to specifically? – djv Jan 22 '19 at 21:19
  • 1
    @SchmellerMeller In your question you were trying to operate on the form rather than the underlying data, and I was just pointing out that you should operate on the data. That is how to solve the problem. Actually my solution is the same as Jimi's (which looks great because he was aware of your previous question). In general, my solution solves the general problem posed in your qestion. Jimi's solves your specific problem because it is tailored to your previous question. – djv Jan 22 '19 at 21:26
  • 1
    My bad. I've just realised it's basically exactly the same. Oops. – SchmellerMeller Jan 22 '19 at 21:34
0

You are currently using 2 separate DataTables. Also you are attempting to add row each time you set a column value. This might work for you.

Private Sub btnAdd_Click(sender As Object, e As EventArgs) Handles btnAdd.Click
    Dim dt As New DataTable()
    AppendColumnsToDGV2(dt)
    For Each row As DataGridViewRow In DataGridView1.Rows
        If row.Cells("SelectColumn").Value = True Then
            Dim NewRow = dt.NewRow
            For i As Integer = 0 To row.Cells.Count - 1
                NewRow(i) = row.Cells(i).Value
            Next
            dt.Rows.Add(NewRow)
        End If
    Next
End Sub

Private Sub AppendColumnsToDGV2(dt As DataTable)
    'dt.Columns.Add(CreateDGVCheckBoxCol())
    'dt.Columns.Add(CreateImageColumn())
    dt.Columns.Add(DataGridView1.Columns(3).HeaderText)
    dt.Columns.Add(DataGridView1.Columns(4).HeaderText)
    dt.Columns.Add(DataGridView1.Columns(5).HeaderText)
    dt.Columns.Add(DataGridView1.Columns(6).HeaderText)
    DataGridView2.DataSource = dt
End Sub
JerryM
  • 910
  • 6
  • 9
  • Thank you for this @JerryM however now I'm getting the error `'Rows cannot be programmatically added to the DataGridView's rows collection when the control is data-bound.'` so then I changed it to `datagridview2.datasource.rows.add(newrow)` and now I'm getting the error `System.ArgumentException: 'This row already belongs to this table.'` – SchmellerMeller Jan 22 '19 at 17:23
  • Oops. Yes. The row should be added to the datatable. I've updated the answer. – JerryM Jan 22 '19 at 17:33
  • Also when using `dt.rows.add(newrow)` I also get `this row already belongs to this table` – SchmellerMeller Jan 22 '19 at 17:34
  • I forgot to follow my own advice about moving the Add method call below then `Next` statement. – JerryM Jan 22 '19 at 17:37