orange database cylinder, hand-drawn storage icon, database label, white background

Button Control in VB Database

2.5K Views
40
700
60
25
60
PCBWay

Hello friends! In this tutorial, we will add Add, Save, Remove and navigation buttons to a Visual Basic database application. We will continue with the contact form from our previous lesson and use its existing dataset, binding source and TableAdapter.

If you have not built that form yet, first follow creating a database in Microsoft Visual Studio. This lesson retains the original Visual Studio 2010 Windows Forms example, with clearer event handlers and explanations of what each button changes.

The key distinction is that editing a bound form changes in-memory data first. Adding a row, removing a row and moving to another contact do not necessarily write anything to SQL Server. Our Save button is responsible for sending the pending changes to the database.

orange database cylinder, hand-drawn storage icon, database label, white background
Figure: A database illustration introduces the Visual Basic contact-management tutorials.

Start with the Existing Contact Form

The first lesson created a contact table and displayed it in both a grid and a details view. Your form should already load contacts successfully before you add the new buttons.

contact records grid, selected record details, navigation toolbar, Visual Basic Windows Form
Figure: The original contact application displays a grid and details controls for the current record.

We will extend it with seven controls: Add, Save, Remove, Previous, Next, Move To Top and Move To End.

contact grid and details, Add Save Remove buttons, Previous and Next controls, first and last record buttons
Figure: The original completed form includes custom record-editing and navigation controls.

The examples assume these generated components:

ComponentPurpose
ContactsDataSetHolds the contact rows in memory.
Contact_FormBindingSourceTracks the current row and supplies data to the controls.
Contact_FormTableAdapterReads and updates the contact table through configured commands.
TableAdapterManagerCoordinates saves for its configured TableAdapters when present.
FullNameTextBoxThe bound details control for the required contact name in the updated example schema.

Check the actual names in your designer. If your original table uses a different name field, adapt the validation control name accordingly. Renaming a button's caption does not rename a binding source or regenerate the database schema.

Place the following methods inside your existing form class. Do not paste a second complete form class into the designer-generated file. If Visual Studio already created an event handler, replace its body or use the supplied handler once; duplicate handlers can run the same operation twice.

Step 1: Add the Add, Save and Remove Buttons

Open the form designer and drag three Button controls from the Toolbox to a convenient area of the form.

Windows Forms contact designer, three unnamed buttons, bound contact fields, existing data grid
Figure: Place three buttons below the details controls for the editing actions.

Set their Name and Text properties separately. Name is the identifier used in code; Text is the caption the user sees.

Add button, Save button, Remove button, contact form designer
Figure: Set clear captions and code names for the three contact-editing buttons.
NameTextIntended action
btnAddAddCreate a new contact in the bound in-memory list.
btnSaveSaveValidate and persist pending contact changes.
btnRemoveRemoveRemove the selected contact from the working data, pending Save.

For the button workflow below, set each custom button's CausesValidation property to False. Add, Save and navigation will explicitly validate through a shared helper. Remove deliberately allows an incomplete new contact to be discarded. Keep validation enabled on the data-entry text boxes.

To keep this example focused, use the details controls for editing and make the grid read-only. Also disable its user-add and user-delete options. This prevents the grid from becoming a second editing path with different validation behavior. Existing BindingNavigator buttons should either be removed from the form or routed through the same actions if you keep them.

Step 2: Add Meaningful Validation

Calling ValidateChildren() is only useful when the form has validation rules. It returns a Boolean result that must be checked before the application proceeds. Ignoring that result can allow a save to continue after a control has rejected its value. See the ValidateChildren reference.

Add an ErrorProvider component named ErrorProvider1 from the Toolbox. For the updated contact schema, set FullNameTextBox.MaxLength to 100 and add this handler:

Private Sub FullNameTextBox_Validating(
    sender As Object,
    e As System.ComponentModel.CancelEventArgs
) Handles FullNameTextBox.Validating

    ErrorProvider1.SetError(FullNameTextBox, "")

    If Contact_FormBindingSource.Count = 0 Then
        Return
    End If

    Dim enteredName As String = FullNameTextBox.Text.Trim()
    If enteredName.Length = 0 OrElse enteredName.Length > 100 Then
        e.Cancel = True
        ErrorProvider1.SetError(
            FullNameTextBox,
            "Enter a contact name from 1 to 100 characters.")
    End If
End Sub

This handler checks the name and displays an error beside its control. It does not write a database row. The Validating event is the point where a control can reject its current value through the event arguments.

Add equivalent rules for fields your application requires. Keep phone numbers as text to preserve formatting and leading zeros. Do not require every optional field merely because a text box exists on the form.

Create a shared helper for finishing an edit

Private Function FinishCurrentEdit() As Boolean
    Try
        If Contact_FormBindingSource.Count = 0 Then
            Return True
        End If

        If Not Me.ValidateChildren() Then
            MessageBox.Show(
                "Please correct the highlighted contact details.",
                "Check contact")
            Return False
        End If

        Contact_FormBindingSource.EndEdit()
        Return True
    Catch ex As Exception
        MessageBox.Show(
            "The current edit could not be completed: " & ex.Message,
            "Contact edit")
        Return False
    End Try
End Function

The empty-list check is intentional. After deleting the last contact, no row remains to validate, but its pending deletion still needs to be saved.

BindingSource.EndEdit completes the edit in the underlying bound data. In this application that data is held in a dataset. EndEdit does not, by itself, execute an SQL update.

Step 3: Implement the Add Button

Private Sub btnAdd_Click(
    sender As Object,
    e As EventArgs
) Handles btnAdd.Click

    If Not FinishCurrentEdit() Then
        Return
    End If

    Try
        Contact_FormBindingSource.AddNew()
        ErrorProvider1.Clear()
        FullNameTextBox.Focus()
    Catch ex As Exception
        MessageBox.Show(
            "A new contact could not be started: " & ex.Message,
            "Add contact")
    End Try
End Sub

After clicking Add, enter the contact's details and then click Save. The new row belongs to the application's working data until a database update succeeds.

The AddNew method also interacts with any pending edit on the current item. Calling our validation helper first prevents repeatedly starting new contacts while leaving a required name empty.

Do not assign ContactID by taking the number of visible rows and adding one. The example table uses a database identity column. Its generated dataset may temporarily use an unsaved identity value; the final key must come from the configured insert and refresh behavior.

Adding a contact creates a row. It does not add a column to the database. Columns describe fields such as name and phone, and changing them is a schema operation.

Step 4: Implement the Save Button

The original form used a TableAdapterManager. If that component exists in your project, make sure its Contact_FormTableAdapter property is assigned to the adapter that loads your contacts. The handler below makes that assignment explicit.

Private Sub btnSave_Click(
    sender As Object,
    e As EventArgs
) Handles btnSave.Click

    If Not FinishCurrentEdit() Then
        Return
    End If

    Try
        Me.TableAdapterManager.Contact_FormTableAdapter =
            Me.Contact_FormTableAdapter

        Dim affected As Integer =
            Me.TableAdapterManager.UpdateAll(Me.ContactsDataSet)

        If affected = 0 Then
            MessageBox.Show("There were no row changes to save.", "Contacts")
        Else
            MessageBox.Show(
                affected.ToString() & " row changes saved.",
                "Contacts")
        End If
    Catch ex As System.Data.DBConcurrencyException
        MessageBox.Show(
            "A contact changed in the database. Review the conflict before retrying.",
            "Save conflict")
    Catch ex As Exception
        MessageBox.Show(
            "The contacts could not be saved: " & ex.Message,
            "Save failed")
    End Try
End Sub

TableAdapterManager is generated for the dataset; it is not a universal component present in every Windows Forms project. Its configured adapters determine which tables it can update. The TableAdapter and TableAdapterManager documentation explains their roles.

If your single-table project has no manager, replace the manager assignment and UpdateAll call with this line inside the same Try block:

Dim affected As Integer =
    Me.Contact_FormTableAdapter.Update(
        Me.ContactsDataSet.Contact_Form)

Choose one save path. Do not add both calls and assume that more update calls make the operation safer.

Understand what Save includes

A dataset tracks row states. Added contacts need inserts, changed contacts need updates and persisted contacts marked for deletion need deletes. Saving the dataset is therefore broader than saving only the row currently displayed.

Suppose you edit contact A, add contact B and remove contact C before pressing Save. The save operation may process all three pending changes. The displayed count represents affected row operations, not the total number of contacts in the table. Microsoft's dataset persistence guide explains this two-stage update model.

Do not call ContactsDataSet.AcceptChanges() before the adapter update. That accepts changes in memory and alters the tracked row states; it does not write them to SQL Server. Clearing that information too early can leave the adapter with nothing to persist. See AcceptChanges and RejectChanges.

Step 5: Implement the Remove Button

Private Sub btnRemove_Click(
    sender As Object,
    e As EventArgs
) Handles btnRemove.Click

    If Contact_FormBindingSource.Count = 0 OrElse
       Contact_FormBindingSource.Current Is Nothing Then
        MessageBox.Show("There is no contact selected.", "Remove contact")
        Return
    End If

    Dim answer As DialogResult = MessageBox.Show(
        "Remove the selected contact? Press Save afterward to store the change.",
        "Remove contact",
        MessageBoxButtons.YesNo,
        MessageBoxIcon.Question,
        MessageBoxDefaultButton.Button2)

    If answer <> DialogResult.Yes Then
        Return
    End If

    Try
        Contact_FormBindingSource.RemoveCurrent()
        ErrorProvider1.Clear()
    Catch ex As Exception
        MessageBox.Show(
            "The contact could not be removed: " & ex.Message,
            "Remove failed")
    End Try
End Sub

RemoveCurrent acts on the current item in the bound list. With our dataset binding, removing an existing persisted row creates a pending deletion. The database row remains until Save sends that deletion successfully.

Removing a new contact that was never saved is different: there is no corresponding database row to delete. The action simply discards that pending addition.

We intentionally do not validate required fields before Remove. A user should be able to discard an incomplete new contact without first inventing a name for it. The confirmation makes the selected action explicit, while the empty-list check handles the case where there is nothing to remove.

Step 6: Add Previous and Next Buttons

Place two more buttons below the editing controls.

contact form designer, two new navigation buttons, existing editing controls, data grid layout
Figure: Add two more buttons below the editing controls for previous and next navigation.

Name them btnPrevious and btnNext, with the captions Previous and Next. Set CausesValidation to False because the shared navigation handler will validate explicitly.

Previous button, Next button, contact details controls, Windows Forms designer
Figure: The two buttons move the binding source to the adjacent record.

The underlying operations are MovePrevious() and MoveNext(). They change the current position in the binding source. They do not rearrange database rows or save pending changes.

We will connect these buttons after adding the first and last controls so that all four use the same handler.

Step 7: Add First and Last Record Buttons

Add the final two buttons in the form designer.

two buttons below the grid, contact form designer, existing navigation controls, record list layout
Figure: Place the first-record and last-record controls beside the contact list.

Name them btnFirst and btnLast. You can keep the original captions Move To Top and Move To End, although First and Last are shorter alternatives. Set CausesValidation to False for these buttons as well.

Move To End button, Move To Top button, contact record grid, Windows Forms designer
Figure: These buttons move to the last or first record in the current bound view.

Now add one handler for all four navigation buttons:

Private Sub Navigation_Click(
    sender As Object,
    e As EventArgs
) Handles btnPrevious.Click, btnNext.Click, btnFirst.Click, btnLast.Click

    If Contact_FormBindingSource.Count = 0 Then
        Return
    End If

    If Not FinishCurrentEdit() Then
        Return
    End If

    Try
        Select Case DirectCast(sender, Button).Name
            Case "btnPrevious"
                Contact_FormBindingSource.MovePrevious()
            Case "btnNext"
                Contact_FormBindingSource.MoveNext()
            Case "btnFirst"
                Contact_FormBindingSource.MoveFirst()
            Case "btnLast"
                Contact_FormBindingSource.MoveLast()
        End Select
    Catch ex As Exception
        MessageBox.Show(
            "The selected contact could not be changed: " & ex.Message,
            "Navigation")
    End Try
End Sub

The Handles clause connects the same method to four Click events. The button's Name selects the movement. Do not also add a second Click handler containing another movement call, or a single click may skip a contact.

For an empty list, the handler returns immediately. With one contact, first and last refer to the same item. With several contacts, the order follows the current bound view, including any active sorting or filtering.

Position is different from ContactID

BindingSource.Position is zero-based. If Position is 2, the third item is selected. Its ContactID could be 3, 17 or another value.

A human-readable position label can use Position + 1 when Count is greater than zero, and display “No contacts” for an empty list. That label describes the current view, not necessarily the number of rows in the whole database.

Handle Failures Without Hiding Them

An empty Catch block makes a failed save look like a successful click. The examples display error messages so that a learning project can reveal missing commands, connection problems or invalid values. A deployed application should provide a suitable user message and record detailed diagnostics through its logging system.

A concurrency conflict can occur when another user changes or removes a row after your form loads it. The configured update command may then find no matching original row. Catching the exception informs the user, but it does not resolve the conflict automatically. Microsoft's optimistic concurrency guide explains why original values matter.

Do not immediately refill the entire dataset in every Catch block. That can replace the user's working data before the conflict is understood. Likewise, do not retry a batch blindly after a partial failure without checking the update and transaction behavior of your chosen save path.

Review the Button Behavior with a Small Example

  1. Open the form and confirm that the existing contacts load.
  2. Click Add, enter a fictional name and save it.
  3. Close and reopen the application to check persistence.
  4. Edit the name, navigate to another record and remember that navigation alone does not save it.
  5. Click Save, reopen the application and inspect the edited contact.
  6. Remove the test contact, then click Save and check that the deletion persists.
  7. Start a new contact with a blank required name and confirm that Save requests a correction.
  8. Use Remove to discard that incomplete contact.

These are exercises to run in your own project, not a claim that the screenshots demonstrate the revised handlers. Use test contacts so that you can repeat the actions without affecting useful records.

SymptomLikely area to inspect
A button does nothing.Its Name, Click event and any displayed validation error.
One click adds two contacts.Duplicate event subscriptions or duplicate AddNew calls.
Save reports no changes unexpectedly.Binding configuration, premature AcceptChanges calls and manager adapter assignments.
Saved records seem to disappear.The runtime database connection and any MDF copy in the build-output folder.
The details and grid select different contacts.Whether both views use the same BindingSource.
Remove fails on an empty table.The current-item and row-count checks.

For a fuller application, add a deliberate unsaved-changes policy when closing the form. Let users save, discard or cancel closing. Also apply the same validation policy to any extra navigation route you enable, such as direct grid selection or retained toolbar buttons.

Review: What Each Button Actually Does

Add starts a new working row, Remove changes the working list, and navigation changes which contact is current. Save validates the active edit and submits tracked changes through the configured data-access components. Keeping those responsibilities clear makes the form easier to extend and its failures easier to diagnose.

In the next part, we continue with updating database tables through VB code. Understanding the difference between a control value, a dataset row and a persisted database record is the foundation for that work.

Frequently Asked Questions

Does AddNew immediately insert a database record?

No. In this dataset-based example, it creates a new item in the bound working data. The adapter update persists it later.

Why does a removed contact return after restarting?

The deletion may not have been saved, the save may have failed, or the application may be opening a different database copy. Check those possibilities before changing the RemoveCurrent call.

Does EndEdit save to SQL Server?

No. It completes the current edit in the bound data. A TableAdapter update or configured TableAdapterManager operation performs persistence.

Can I use the original Button1 and Button2 names?

Yes, but every Handles clause and name comparison must match them. Descriptive names reduce confusion as the form grows.

Will Save update only the selected contact?

The examples save pending changes in the supplied table or managed dataset. Earlier edits and deletions can be included even when another contact is currently selected.

Can CancelEdit undo everything since the last save?

Do not assume so. Canceling a current binding edit and rejecting all tracked dataset changes have different scopes. Define the intended undo behavior before adding a Cancel button.


Comments

14

Join the conversation

1 reply
1 reply
Reply4

I was following the steps until "Step 1 : Add, Save & Remove Buttons" where there was an error that states 'Contact_Form BindingSource' not part of my project, please help.

1 reply