Bind a database result to a grid
Load records asynchronously and handle selection safely.
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
{
"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 property | Value |
|---|---|
Caption | "DataGrid — McBasic" |
Width | 820 |
Height | 520 |
MinWidth | 700 |
MinHeight | 420 |
| Type | Name | Parent |
|---|---|---|
| Label | lblTitle | Form |
| Label | lblStatus | Form |
| CommandButton | cmdQuit | Form |
| DataGrid | grdProducts | Form |
lblTitle · exact Inspector values
| Property | Value |
|---|---|
Left | 24 |
Top | 20 |
Width | 500 |
Height | 32 |
Caption | "Products" |
TabIndex | 0 |
| Anchor edges | Left, Top |
lblStatus · exact Inspector values
| Property | Value |
|---|---|
Left | 24 |
Top | 54 |
Width | 560 |
Height | 30 |
Caption | "Starting..." |
TabIndex | 1 |
| Anchor edges | Left, Top |
cmdQuit · exact Inspector values
| Property | Value |
|---|---|
Left | 680 |
Top | 20 |
Width | 110 |
Height | 36 |
Caption | "Quit" |
TabIndex | 2 |
| Anchor edges | Right, Top |
grdProducts · exact Inspector values
| Property | Value |
|---|---|
Left | 24 |
Top | 96 |
Width | 766 |
Height | 380 |
TabIndex | 3 |
IsReadOnly | True |
CanUserSortColumns | True |
CanUserResizeColumns | True |
HeadersVisible | True |
| Anchor edges | Left, Top, Right, Bottom |
Step 3Connect the events explicitly#
MainForm
| Object | Event | Procedure | Parameters |
|---|---|---|---|
cmdQuit | Click | cmdQuit_Click | Click() |
grdProducts | SelectionChanged | grdProducts_SelectionChanged | SelectionChanged() |
| Form | Shown | Form_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.
Option Explicit
Private mDb As Database
Private mRows As BindingSourcePrivate Async Sub Form_Shown()
Initialize state before enabling dependent actions. Keep the shared declarations and startup dependencies with this procedure.
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 SubPrivate 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.
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 FunctionPrivate Sub grdProducts_SelectionChanged()
Read the current value or selection and update its dependent interface. The binding above determines which object calls this handler.
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 SubPrivate Sub cmdQuit_Click()
Enter the complete action, including its input checks and calls to helper procedures.
Private Sub cmdQuit_Click()
App.Quit
End SubCopy the complete Forms/MainForm.bas file
Replace the file once; do not append duplicate procedures.
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 SubStep 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:
mcbasic check DataGrid.mcproj
mcbasic run DataGrid.mcprojAcceptance 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.
20f4bfed6b · Build manifest