McBasic™Get McBasic™ ↗
Build 881
Hands-on tutorial · Data & services

Bind a database result to a grid

Load records asynchronously and handle selection safely.

1 Form / control designs1 complete BASIC filesGuided sample project

The recordset is converted to a BindingSource before it reaches the grid. That binding is the loaded view of data, not a live SQL table. Check the selected index before reading a row. Distinguish empty results from a failed load so the user does not mistake an error for “no records”.

Step 1Prepare a safe practice project#

Extract the archive into a new writable folder, separate from your own application. Open DataGrid.mcproj in McBasic™. Keep every declared source, layout, resource and migration file together.

To reconstruct it yourself, create a DesktopApp project and add the files declared below. In the complete project, follow these steps as a guided rebuild: replace each named source file once and Check after all its procedures are present.

Exact project configuration
DataGrid.mcproj
{
  "schemaVersion": 1,
  "projectId": "a1ccf2f1-8f80-45f6-9c3a-0a6e8f2ef8c8",
  "name": "DataGridSample",
  "kind": "DesktopApp",
  "language": {
    "dialect": "McBasic1",
    "requireExplicit": true,
    "compare": "Binary",
    "defaultArrayBase": 0
  },
  "application": {
    "bundleIdentifier": "com.mcbasic.samples.datagrid",
    "version": "0.1.0",
    "buildNumber": "1",
    "startupFormId": "form-main",
    "shutdownMode": "OnStartupFormClosed"
  },
  "forms": [
    {
      "id": "form-main",
      "typeName": "MainForm",
      "layout": "Forms/MainForm.form.json",
      "code": "Forms/MainForm.bas"
    }
  ],
  "modules": [],
  "classes": [],
  "tests": [],
  "references": [],
  "resources": [],
  "targets": [
    "osx-arm64"
  ],
  "capabilities": []
}

Step 2Build the Form and set its properties#

MainForm

Select the Form first and set its properties. Add containers before their children. These are initial Designer values, not statements to paste into a procedure.

Form propertyValue
Caption"DataGrid — McBasic"
Width820
Height520
MinWidth700
MinHeight420
TypeNameParent
LabellblTitleForm
LabellblStatusForm
CommandButtoncmdQuitForm
DataGridgrdProductsForm
lblTitle · exact Inspector values
PropertyValue
Left24
Top20
Width500
Height32
Caption"Products"
TabIndex0
Anchor edgesLeft, Top
lblStatus · exact Inspector values
PropertyValue
Left24
Top54
Width560
Height30
Caption"Starting..."
TabIndex1
Anchor edgesLeft, Top
cmdQuit · exact Inspector values
PropertyValue
Left680
Top20
Width110
Height36
Caption"Quit"
TabIndex2
Anchor edgesRight, Top
grdProducts · exact Inspector values
PropertyValue
Left24
Top96
Width766
Height380
TabIndex3
IsReadOnlyTrue
CanUserSortColumnsTrue
CanUserResizeColumnsTrue
HeadersVisibleTrue
Anchor edgesLeft, Top, Right, Bottom

Step 3Connect the events explicitly#

MainForm

ObjectEventProcedureParameters
cmdQuitClickcmdQuit_ClickClick()
grdProductsSelectionChangedgrdProducts_SelectionChangedSelectionChanged()
FormShownForm_Shown()

Step 4Enter the complete behavior#

Open the named source file. Enter declarations first, then every procedure. Expand a procedure for its full body, or copy the complete file. Helpers are required: do not stop after entering the first Click handler.

Forms/MainForm.bas

Enter these declarations at file scope, before the procedures.

Forms/MainForm.bas · declarations
Option Explicit

Private mDb As Database
Private mRows As BindingSource
Private Async Sub Form_Shown()

Initialize state before enabling dependent actions. Keep the shared declarations and startup dependencies with this procedure.

Forms/MainForm.bas · Form_Shown
Private Async Sub Form_Shown()
    Dim migrations As MigrationSet
    Dim command As DbCommand
    Dim ignored As Variant

    lblStatus.Caption = "Opening SQLite database..."
    cmdQuit.Enabled = False
    Try
        Set mDb = Await Sqlite.OpenAsync(AppPaths.DataFile("datagrid.db"))
        Set migrations = Sqlite.CreateMigrationSet()
        migrations.Add 1, "CREATE TABLE IF NOT EXISTS Products (ID INTEGER PRIMARY KEY, Name TEXT NOT NULL, Stock INTEGER NOT NULL, Price REAL NOT NULL)"
        ignored = Await mDb.ApplyMigrationsAsync(migrations)
        Set command = mDb.CreateCommand("INSERT OR IGNORE INTO Products (ID, Name, Stock, Price) VALUES (1, 'Apple', 42, 1.25), (2, 'Apricot', 18, 2.10), (3, 'Banana', 73, 0.85), (4, 'Blueberry', 31, 3.40), (5, 'Cherry', 26, 4.15), (6, 'Grape', 64, 2.75), (7, 'Kiwi', 14, 1.90), (8, 'Mango', 22, 2.45), (9, 'Orange', 57, 1.15), (10, 'Pear', 39, 1.60), (11, 'Plum', 28, 2.05), (12, 'Raspberry', 16, 3.85)")
        ignored = Await command.ExecuteAsync()
        ignored = Await RefreshProducts()
        cmdQuit.Enabled = True
    Catch failure As McException
        lblStatus.Caption = "Database error " & CStr(failure.Number) & ": " & failure.Message
        cmdQuit.Enabled = True
    End Try
End Sub
Private Async Function RefreshProducts() As Boolean

Follow the success, failure and cleanup paths together. Do not omit a guard or Finally block when entering this procedure.

Forms/MainForm.bas · RefreshProducts
Private Async Function RefreshProducts() As Boolean
    Dim command As DbCommand
    Dim rows As Recordset

    Try
        Set command = mDb.CreateCommand("SELECT ID, Name, Stock, Price FROM Products ORDER BY ID")
        Set rows = Await command.QueryAsync()
        Set mRows = rows.Bind()
        mRows.Require "Name"
        Set grdProducts.DataSource = mRows
        grdProducts.SelectedIndex = -1
        lblStatus.Caption = CStr(mRows.RowCount) & " products loaded"
        RefreshProducts = True
    Catch failure As McException
        lblStatus.Caption = "Database error " & CStr(failure.Number) & ": " & failure.Message
        RefreshProducts = False
    End Try
End Function
Private Sub grdProducts_SelectionChanged()

Read the current value or selection and update its dependent interface. The binding above determines which object calls this handler.

Forms/MainForm.bas · grdProducts_SelectionChanged
Private Sub grdProducts_SelectionChanged()
    Dim row As Long

    If mRows Is Nothing Then Exit Sub
    row = CInt(grdProducts.SelectedIndex)
    If row < 0 Or row >= mRows.RowCount Then Exit Sub
    lblStatus.Caption = "Selected product: " & CStr(mRows.Value(row, "Name"))
End Sub
Private Sub cmdQuit_Click()

Enter the complete action, including its input checks and calls to helper procedures.

Forms/MainForm.bas · cmdQuit_Click
Private Sub cmdQuit_Click()
    App.Quit
End Sub
Copy the complete Forms/MainForm.bas file

Replace the file once; do not append duplicate procedures.

Forms/MainForm.bas
Option Explicit

Private mDb As Database
Private mRows As BindingSource

Private Async Sub Form_Shown()
    Dim migrations As MigrationSet
    Dim command As DbCommand
    Dim ignored As Variant

    lblStatus.Caption = "Opening SQLite database..."
    cmdQuit.Enabled = False
    Try
        Set mDb = Await Sqlite.OpenAsync(AppPaths.DataFile("datagrid.db"))
        Set migrations = Sqlite.CreateMigrationSet()
        migrations.Add 1, "CREATE TABLE IF NOT EXISTS Products (ID INTEGER PRIMARY KEY, Name TEXT NOT NULL, Stock INTEGER NOT NULL, Price REAL NOT NULL)"
        ignored = Await mDb.ApplyMigrationsAsync(migrations)
        Set command = mDb.CreateCommand("INSERT OR IGNORE INTO Products (ID, Name, Stock, Price) VALUES (1, 'Apple', 42, 1.25), (2, 'Apricot', 18, 2.10), (3, 'Banana', 73, 0.85), (4, 'Blueberry', 31, 3.40), (5, 'Cherry', 26, 4.15), (6, 'Grape', 64, 2.75), (7, 'Kiwi', 14, 1.90), (8, 'Mango', 22, 2.45), (9, 'Orange', 57, 1.15), (10, 'Pear', 39, 1.60), (11, 'Plum', 28, 2.05), (12, 'Raspberry', 16, 3.85)")
        ignored = Await command.ExecuteAsync()
        ignored = Await RefreshProducts()
        cmdQuit.Enabled = True
    Catch failure As McException
        lblStatus.Caption = "Database error " & CStr(failure.Number) & ": " & failure.Message
        cmdQuit.Enabled = True
    End Try
End Sub

Private Async Function RefreshProducts() As Boolean
    Dim command As DbCommand
    Dim rows As Recordset

    Try
        Set command = mDb.CreateCommand("SELECT ID, Name, Stock, Price FROM Products ORDER BY ID")
        Set rows = Await command.QueryAsync()
        Set mRows = rows.Bind()
        mRows.Require "Name"
        Set grdProducts.DataSource = mRows
        grdProducts.SelectedIndex = -1
        lblStatus.Caption = CStr(mRows.RowCount) & " products loaded"
        RefreshProducts = True
    Catch failure As McException
        lblStatus.Caption = "Database error " & CStr(failure.Number) & ": " & failure.Message
        RefreshProducts = False
    End Try
End Function

Private Sub grdProducts_SelectionChanged()
    Dim row As Long

    If mRows Is Nothing Then Exit Sub
    row = CInt(grdProducts.SelectedIndex)
    If row < 0 Or row >= mRows.RowCount Then Exit Sub
    lblStatus.Caption = "Selected product: " & CStr(mRows.Value(row, "Name"))
End Sub

Private Sub cmdQuit_Click()
    App.Quit
End Sub

Step 5Keep resources and storage reproducible#

No external resource or migration files are declared. Any inline migration, seed data or local JSON is present in the code above. Runtime application data and private preferences are not included in the archive.

Step 6Check, run and test the result#

Save all files and run Check. Resolve diagnostics before Run. Developer CLI users can work from the extracted folder:

Developer CLI
mcbasic check DataGrid.mcproj
mcbasic run DataGrid.mcproj

Acceptance checklist

  • Run against a fresh sample database and inspect seeded products.
  • Select a row and verify its displayed detail.
  • Refresh or reopen and confirm selection handling remains safe.

Step 7Make one change and repeat the tests#

Add a displayed database column without changing the record’s primary key.

Save a working Git checkpoint first. Change one behavior, Check again and repeat the acceptance checklist. Compare with the downloaded solution if a regression appears.

Source baseline 20f4bfed6b · Build manifest