Link to home
Start Free TrialLog in
Avatar of gem56
gem56

asked on

Problem implementing data binding with DataGridView

Hi guys,
This question is related to my previous question "https://www.experts-exchange.com/questions/22734276/Problem-implementing-data-binding-with-DataGridView.html". As the initial question was answered, and me having additional issues, I decided to open (this) new question.

The code below shows an example that I'm using to data-bind a DataGridView control with an object collection. To run this code just create a Form with a DataGridView control and two buttons (called grdCustomers, btnObjectAddRow and btnDGVAddRow), that I use to: 1) add a new object to the collection and 2) add a row to the DatGridView control.

I am able to (programmatically) delete & modify data/rows via either DataGridView and the Collection bject however I can only add rows via the underlying data and not the DataGridView. When I try to add a row into DataGridView (using button btnDGVAddRow) I get an error message "Rows cannot be programmatically added to the DataGridView's rows collection when the control data-bound".

/Michael

------------------------------------------------------------------------------------------------------------------

Imports System.ComponentModel ' Needed for using <Bindable(True)>

Public Class frmCustomers
    Dim Customers As New CustomersList

    Private Sub frmCustomers_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Try
            LoadCustomers()
            Me.grdCustomers.DataSource = Customers
            Me.grdCustomers.Refresh()
        Catch ex As Exception
            MsgBox("Error in '" & Mid(ex.StackTrace, InStrRev(ex.StackTrace, "\") + 1) & "' - " & ex.Message, MsgBoxStyle.Exclamation)
        End Try
    End Sub

    Private Sub LoadCustomers()
        Try
            Dim l As IList = CType(Customers, IList)
            For n As Int16 = 1 To 5
                l.Add(ReadCustomer(l.Count))
            Next
        Catch ex As Exception
            MsgBox("Error in '" & Mid(ex.StackTrace, InStrRev(ex.StackTrace, "\") + 1) & "' - " & ex.Message, MsgBoxStyle.Exclamation)
        End Try
    End Sub

    Private Function ReadCustomer(ByVal n As Int16, Optional ByVal bEmpty As Boolean = False) As Customer
        Dim cust As New Customer(Customers)
        If bEmpty = False Then
            cust.FirstName = "First" & n
            cust.LastName = "Last" & n
        End If
        Return cust
    End Function

    Private Sub grdCustomers_CancelRowEdit(ByVal sender As Object, ByVal e As System.Windows.Forms.QuestionEventArgs)

    End Sub

    Private Sub grdCustomers_CellBeginEdit(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellCancelEventArgs)

    End Sub

    Private Sub grdCustomers_CellEndEdit(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs)

    End Sub

    Private Sub grdCustomers_CellEnter(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs)

    End Sub

    Private Sub grdCustomers_CellLeave(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs)

    End Sub

    Private Sub btnObjectAddRow_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnObjectAddRow.Click
        Dim cust As New Customer(Me.Customers)
        cust.FirstName = "First" & Me.grdCustomers.Rows.Count + 1
        cust.LastName = "Last" & Me.grdCustomers.Rows.Count + 1
        Me.Customers.Insert(Me.grdCustomers.CurrentRow.Index, cust)
    End Sub

    Private Sub btnObjectDeleteRow_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnObjectDeleteRow.Click
        Me.Customers.RemoveAt(Me.grdCustomers.CurrentRow.Index)
    End Sub

    Private Sub btnObjectModifyRow_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnObjectModifyRow.Click
        With Me.Customers.Item(Me.grdCustomers.CurrentRow.Index)
            If Me.grdCustomers.CurrentCell.ColumnIndex = 0 Then
                .LastName = .LastName & Format(Now, "ss")
            Else
                .FirstName = .FirstName & Format(Now, "ss")
            End If
        End With
    End Sub

    Private Sub btnDGVAddRow_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnDGVAddRow.Click
        With Me.grdCustomers.CurrentCell
            Try
                Me.grdCustomers.Rows.Insert(Me.grdCustomers.CurrentCell.RowIndex)
            Catch ex As Exception
                MsgBox("Error in '" & Mid(ex.StackTrace, InStrRev(ex.StackTrace, "\") + 1) & "' - " & ex.Message, MsgBoxStyle.Exclamation)
            End Try
        End With
    End Sub

    Private Sub btnDGVDeleteRow_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnDGVDeleteRow.Click
        Me.grdCustomers.Rows.RemoveAt(Me.grdCustomers.CurrentRow.Index)
    End Sub

    Private Sub btnDGVModifyRow_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnDGVModifyRow.Click
        With Me.grdCustomers.CurrentCell
            .Value = .Value & Format(Now, "ss")
        End With
    End Sub

    Private Sub btnObjectReset_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnObjectReset.Click
        Customers.Clear()
        LoadCustomers()
    End Sub

    Private Sub btnDGVReset_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnDGVReset.Click
        Me.grdCustomers.Rows.Clear()
        LoadCustomers()
    End Sub

End Class




Public Class CustomersList
    Inherits BindingList(Of Customer)

    Friend Sub CustomerChanged(ByVal cust As Customer)
        Dim index As Integer = Me.IndexOf(cust)
        OnListChanged(New ListChangedEventArgs(ListChangedType.ItemChanged, index))
    End Sub

End Class




Public Class Customer
    Implements IEditableObject
    Implements INotifyPropertyChanged

    Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged

    Structure CustomerData
        Friend firstName As String
        Friend lastName As String
    End Structure

    Private custData As CustomerData
    Private backupData As CustomerData
    Private inTxn As Boolean = False

    Sub BeginEdit() Implements IEditableObject.BeginEdit
        If Not inTxn Then
            Me.backupData = custData
            inTxn = True
        End If
    End Sub

    Sub CancelEdit() Implements IEditableObject.CancelEdit
        If inTxn Then
            custData = backupData
            inTxn = False
        End If
    End Sub

    Sub EndEdit() Implements IEditableObject.EndEdit
        If inTxn Then
            backupData = New CustomerData()
            inTxn = False
        End If
    End Sub

    Public Sub New(ByVal objMyParent As CustomersList)
        _MyParent = objMyParent
        custData = New CustomerData()
        custData.firstName = ""
        custData.lastName = ""
    End Sub

    Public Sub New()

    End Sub

    Public Property FirstName() As String
        Get
            Return custData.firstName
        End Get
        Set(ByVal Value As String)
            custData.firstName = Value
            Me.OnCustomerChanged()
            Dim PropName As String = System.Reflection.MethodBase.GetCurrentMethod.Name
            RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs(PropName.Remove(0, 4)))
        End Set
    End Property

    Public Property LastName() As String
        Get
            Return custData.lastName
        End Get
        Set(ByVal Value As String)
            custData.lastName = Value
            Me.OnCustomerChanged()
            Dim PropName As String = System.Reflection.MethodBase.GetCurrentMethod.Name
            RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs(PropName.Remove(0, 4)))
        End Set
    End Property

    Private _MyParent As CustomersList
    Friend Property MyParent() As CustomersList
        Get
            Return _MyParent
        End Get
        Set(ByVal Value As CustomersList)
            _MyParent = Value
            Dim PropName As String = System.Reflection.MethodBase.GetCurrentMethod.Name
            RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs(PropName.Remove(0, 4)))
        End Set
    End Property

    Private Sub OnCustomerChanged()
        If Not inTxn And Not (_MyParent Is Nothing) Then
            _MyParent.CustomerChanged(Me)
        End If
    End Sub

End Class
Avatar of Ajay Sharma
Ajay Sharma
Flag of India image

Avatar of Sancler
Sancler

Can I ask WHY you want to do this?  First, I think if you use the "New" line in the datagridview - the last one, marked with "*" - it will work (tho' by user entry, rather than programmatically).  Secondly, if you want to do it programmatically, why not simply add it to the collection and let the binding then display it in the datagridview?  So, for instance, this works for me

    Private Sub btnDGVAddRow_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnDGVAddRow.Click
        With Me.grdCustomers.CurrentCell
            Try
                Dim cust As New Customer(Me.Customers)
                cust.FirstName = "" '<< you put what you want here ...
                cust.LastName = "" '... and here
                Me.Customers.Insert(Me.grdCustomers.CurrentRow.Index, cust)
            Catch ex As Exception
                MsgBox("Error in '" & Mid(ex.StackTrace, InStrRev(ex.StackTrace, "\") + 1) & "' - " & ex.Message, MsgBoxStyle.Exclamation)
            End Try
        End With
    End Sub

I'm not saying that it might not be possible.  I haven't thought it through.  But the end result seems to me to be readily achievable without such thought.  And, in my view, doing it in the collection rather than trying to do it via the DataGridView is more consistent with the model with which we're dealing.  The DataGridView is really just a Display (and Editing) mechanism for the data which is actually in the collection.  If the _user_ has to see, add to, alter, delete such data then it is the DataGridView that provides the necessary user-interface.  But if the _program_ needs to do any of those things it has direct access to the collection.  Once the program has done any of those things, they do need to be reflected in what the user sees - but the binding copes with that.  So, using the program to alter the _datagridview_ is just introducing an unnecessary step in the process.  Rather than the program telling the collection and the binding passing that on to the datagridview, the program has to tell the datagridview then the binding has to tell the collection then the binding has to tell the datagridview.

So, as I say, why?

Roger
Avatar of gem56

ASKER

Thanks for your contribution ajaysharmaapjs however after taking a look at them they seem fairly generic and don't take into account data-binding aspects, which is my real issue here.

Roger, I'll give you my reasons for asking this question even though what you are saying makes sense and is the way that I currently have it working.

I'm making a control that has a DataGridView and some buttons (as well as a pop-up menu) to provide functions that add/delete/duplicate/move/etc. rows. As I plan to use this control in a number of forms (each with slightly different functionality) I thought that the more functionality that is located in the control the less that I need to repeat in the form. So I'm just trying to see whether this approach is possible, even though I'm not sure if it's the best approach to take considering some of the points that you raised.

/Michael

ASKER CERTIFIED SOLUTION
Avatar of Sancler
Sancler

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of gem56

ASKER

Roger,
Considering your comments, and the ones that I already got earlier, it doesn't sound like part of standard functionality so it would not be part of my consideration.

Thanks,
     Michael