Page 1 of 1

Synchronise TextBoxes with DataGridView Rate Topic: -----

#1 andrewsw  Icon User is offline

  • I'd like to return this printer
  • member icon

Reputation: 6322
  • View blog
  • Posts: 25,422
  • Joined: 12-December 12

Posted 22 October 2015 - 04:27 AM

This tutorial demonstrates synchronising the values displayed in TextBoxes with the current row of a DataGridView. It also serves as a basic introduction to data binding. I'm using TextBoxes but other control-types could be used. I also demonstrate using an Expression to display a combination of two cell-values (firstname + lastname).

This is a common question and often a solution is offered that uses some event of the DGV (such as CellContentClick), or clicking of a Button, to manually change the Text of TextBoxes. These solutions might answer the immediate question, but the opportunity should be taken to encourage the person to investigate, and pursue, data binding. The DGV should be bound to a data source, and TextBoxes to data members of this data source, so that they will automatically remain synchronised. The values displayed in the individual controls will automatically reflect the current row of the DGV without any additional event-code.

We need a new WinForms application with a DataGridView (DGV), named frmWithTextBoxes and dgvData. The three TextBoxes are named txtID, txtFirstName and txtLastName.

Attached Image

For this simple demonstration I manually create and populate a DataTable. In reality, there is likely to be a database (or other data store) and a DataAdapter is used to fill a DataTable or DataSet. See the example here:

How to: Bind Data to the Windows Forms DataGridView Control



Rather than binding directly to a data store the next step, and the more professional approach, is to use object-relational mapping to bind to a "virtual object database", perhaps using a framework such as Entity Framework. Essentially, programming happens with objects and properties which mirror the needed rows and columns of a database, without the need to write SQL statements directly against the database.



Please do not be distracted by the manual creation and populating of a DataTable for the purpose of this tutorial. The important purpose is to demonstrate binding the DGV to a data source of some kind, and binding other controls to the same data source so that they remain in sync.

Rather than binding directly to the DataTable I am binding to a BindingSource:

MSDN said:

The BindingSource component serves many purposes. First, it simplifies binding controls on a form to data by providing currency management, change notification, and other services between Windows Forms controls and data sources. This is accomplished by attaching the BindingSource component to your data source using the DataSource property. For complex binding scenarios you can optionally set the DataMember property to a specific column or list in the data source. You then bind controls to the BindingSource. All further interaction with the data is accomplished with calls to the BindingSource component.

Double-click an empty area of the form to go to the code-behind and create the Load event-stub.
Public Class frmWithTextBoxes
    Private bsData As New BindingSource()

    Private Sub frmWithTextBoxes_Load(sender As Object, e As EventArgs) Handles MyBase.Load


I only retain a class-level reference to the BindingSource. With a more detailed project references might also be kept to a DataSet and/or DataTable.

Within the Load event I then create a DataTable, define its columns, and add a few rows of data. Refer to the DataTable link provided earlier if you need information about this process, although the code here is fairly easy to follow.
        ' Create a new DataTable.
        Dim table As DataTable = New DataTable("data")
        Dim column As DataColumn
        Dim row As DataRow

        ' Create first column and add to the DataTable.
        column = New DataColumn()
        column.DataType = System.Type.GetType("System.Int32")
        column.ColumnName = "ID"
        column.AutoIncrement = True
        column.Caption = "ID"
        column.ReadOnly = True
        column.Unique = True
        ' Add the column to the DataColumnCollection.
        table.Columns.Add(column)

        column = New DataColumn()
        column.DataType = System.Type.GetType("System.String")
        column.ColumnName = "FirstName"
        column.AutoIncrement = False
        column.Caption = "First Name"
        column.ReadOnly = False
        column.Unique = False
        table.Columns.Add(column)

        column = New DataColumn()
        column.DataType = System.Type.GetType("System.String")
        column.ColumnName = "LastName"
        column.AutoIncrement = False
        column.Caption = "Last Name"
        column.ReadOnly = False
        column.Unique = False
        table.Columns.Add(column)

        row = table.NewRow()
        row("FirstName") = "Bob"
        row("LastName") = "Biscuit"
        table.Rows.Add(row)

        row = table.NewRow()
        row("FirstName") = "Terry"
        row("LastName") = "Tooltip"
        table.Rows.Add(row)

        row = table.NewRow()
        row("FirstName") = "Mary"
        row("LastName") = "Tattles"
        table.Rows.Add(row)


Spoiler

After the above (still in the Load event) follows the most significant code:
        Me.dgvData.DataSource = Me.bsData
        Me.bsData.DataSource = table


The DataSource of the DGV is set to the BindingSource. The BindingSource hasn't been configured in any way yet, but it is with the second line:
        Me.bsData.DataSource = table


The DataSource of the BindingSource is set to the DataTable.

After this, and essential for the purpose of this tutorial, the TextBoxes are bound to individual columns of the BindingSource (that is, columns of the DataTable):
        Me.txtID.DataBindings.Add(New Binding("Text", Me.bsData, "ID"))
        Me.txtFirstName.DataBindings.Add(New Binding("Text", Me.bsData, "FirstName"))
        Me.txtLastName.DataBindings.Add(New Binding("Text", Me.bsData, "LastName"))


"Text" specifies that it is the Text property of the TextBox that is bound to the column-value.

Run the application. As you click between rows of the DGV the text in the TextBoxes mirrors the current row of the DGV. We haven't needed to write any additional event-code to achieve this.

You can edit a value using either the DGV or one of the TextBoxes, although you will have to move away from the row and back again to see the change reflected. More typically, the TextBoxes will display data that is not already visible in the DGV, or the BindingSource is configured to display data from a related, parent record, so this behaviour is not usually noticed or a concern.

With object-relational mapping, changes to values (to properties) can be immediately fed-back (notified) to the UI, without the need to move away from a row.

Using an Expression, Creating a Calculated Field

To display the fullname (firstname + lastname) in a TextBox, or as a column in the DGV, we first add a column to the DataTable using DataColumn.Expression.

Attached Image
        column = New DataColumn()
        column.DataType = System.Type.GetType("System.String")
        column.ColumnName = "FullName"

        column.Expression = "FirstName + ' ' + LastName"

        column.AutoIncrement = False
        column.Caption = "Full Name"
        Table.Columns.Add(column)


(The ReadOnly and Unique properties cannot be set when using Expression, they are not relevant.)
(Note that it is the editor here at DIC that inserts the b-tag within the word Expression.)

Modify the existing code to the following:
        Me.dgvData.DataSource = Me.bsData
        Me.bsData.DataSource = table

        'Me.dgvData.Columns("FullName").Visible = False

        Me.txtID.DataBindings.Add(New Binding("Text", Me.bsData, "ID"))
        Me.txtFirstName.DataBindings.Add(New Binding("Text", Me.bsData, "FirstName"))
        Me.txtLastName.DataBindings.Add(New Binding("Text", Me.bsData, "LastName"))

        Me.txtFullName.DataBindings.Add(New Binding("Text", Me.bsData, "FullName"))


Once you've tested this, uncomment the .Visible = False line if you don't want to display the fullname in the DGV.

You might notice that, if you only provide a firstname, the fullname box is empty. To correct this you can use functions such as ISNULL and IIF in the Expression. For example, "FirstName + ' ' + ISNULL(LastName,'')".



Full code, if needed:

Spoiler

This post has been edited by andrewsw: 23 October 2015 - 09:21 AM


Is This A Good Question/Topic? 2
  • +

Replies To: Synchronise TextBoxes with DataGridView

#2 andrewsw  Icon User is offline

  • I'd like to return this printer
  • member icon

Reputation: 6322
  • View blog
  • Posts: 25,422
  • Joined: 12-December 12

Posted 22 October 2015 - 03:07 PM

I decided to add a Salary column and show how to use DataTable.Compute to display a total salary.

See the full code below. I add a Decimal Salary column and set its DefaultValue to 0.0.
        Me.txtTotalSalary.Text = table.Compute("Sum(Salary)", "True")

        AddHandler dgvData.CellValueChanged, AddressOf UpdateTotal
        AddHandler dgvData.RowsAdded, AddressOf UpdateTotal
        AddHandler dgvData.RowsRemoved, AddressOf UpdateTotal
    End Sub

    Private Sub UpdateTotal()
        Me.txtTotalSalary.Text = table.Compute("Sum(Salary)", "True")
    End Sub

(The declaration of table has been moved to class-level.)

I started to explore getting the value to update automatically with DGV events, but there are also DataTable and BindingSource events. I didn't pursue this very far. (With an ORM the updating process would be handled differently.)

Spoiler

This post has been edited by andrewsw: 22 October 2015 - 05:08 PM

Was This Post Helpful? 1
  • +
  • -

Page 1 of 1