[Webinar] Streamline your web hosting managementRegister Today

x
?
Solved

Problem implementing data binding with DataGridView

Posted on 2007-08-03
5
Medium Priority
?
414 Views
Last Modified: 2013-11-05
Hi guys,
This question is related to my previous question "http://www.experts-exchange.com/Programming/Languages/.NET/Visual_Basic.NET/Q_22734276.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
0
Comment
Question by:gem56
  • 2
  • 2
5 Comments
 
LVL 34

Expert Comment

by:Sancler
ID: 19630737
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
0
 

Author Comment

by:gem56
ID: 19632832
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

0
 
LVL 34

Accepted Solution

by:
Sancler earned 1000 total points
ID: 19633716
No, I don't think it is _directly_ possible.  My belief (which is based on empirical observation rather than on any inside knowledge of the technology) is that the clue lies in the fact that the grid is _bound_.  If you look at the docs for the DataGridViewRowCollection's Insert and Add methods, you will find that both of them say that they will fail, inter alia, if

>>
The DataSource property of the DataGridView is not a null reference (Nothing in Visual Basic).
<<

They also both include the often repeated mantra

>>
This method supports the .NET Framework infrastructure and is not intended to be used directly from your code.
<<

but I'm generally happy to ignore that, if the methods look useful for my purpose.

The first of those makes me believe that the methods are really there to provide for, and only provide for, an _unbound_ grid.  The second of them suggests, though, that the Add method may be being used, in the background, for the _bound_ DGV's inbuilt New row facility.

I have, however, not found a way to tap directly into it.  What I have been able to do, and it's relatively simple, is tap into the next step of the process.  That is, use the AddNew method of the relevant currency manager

    CType(BindingContext(Customers),CurrencyManager).AddNew

[by the way, I've just typed that onto the screen, so if you want to try it out, the syntax will need checking].  But, because that is _add_ing a row (the CurrencyManager has no Insert method) it will, as with any other addition, go at the end of the datatable (hence the grid).  And it really, to my mind, only reinforces the point that I made earlier that the operation is just making work for the system: that is, putting the instruction for a new row at a stage where it will then have to be passed on to the datatable actually to execute and then the result will have to be passed back (through the dame currency manager object) to the DGV.

There are also, quite possibly, I think some other technical considerations: e.g. the rowstate of the resulting row.  The DGV's own inbuilt New row facility results initially in a .Detached row whereas the datatable.Rows.Add(newrow) produces a row with an .Added rowstate.  I'm not absolutely certain what it would be in this case.

You'll have to decide whether your WHY is good enough to be worth pursuing the idea.  Even given your explanation, I think my own inclination would be to drop it.

Roger
0
 

Author Comment

by:gem56
ID: 19633961
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
0

Featured Post

Receive 1:1 tech help

Solve your biggest tech problems alongside global tech experts with 1:1 help.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Wouldn’t it be nice if you could test whether an element is contained in an array by using a Contains method just like the one available on List objects? Wouldn’t it be good if you could write code like this? (CODE) In .NET 3.5, this is possible…
Calculating holidays and working days is a function that is often needed yet it is not one found within the Framework. This article presents one approach to building a working-day calculator for use in .NET.
How can you see what you are working on when you want to see it while you to save a copy? Add a "Save As" icon to the Quick Access Toolbar, or QAT. That way, when you save a copy of a query, form, report, or other object you are modifying, you…
Enter Foreign and Special Characters Enter characters you can't find on a keyboard using its ASCII code ... and learn how to make a handy reference for yourself using Excel ~ Use these codes in any Windows application! ... whether it is a Micr…

590 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question