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

Bind choices to stable record IDs

Display a friendly name while preserving a typed database value.

1 Form / control designs1 complete BASIC filesGuided sample project

The sample binds the same recordset to ComboBox and ListBox. Display columns are not the record identity: SelectedValue carries ID. BeginEdit and Delete alter an in-memory draft; CancelEdit restores that draft. They do not execute a SQL DELETE.

Step 1Prepare a safe practice project#

Extract the archive into a new writable folder, separate from your own application. Open ChoiceBinding.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
ChoiceBinding.mcproj
{
  "schemaVersion": 1,
  "projectId": "b72b7cbe-988d-4ca1-8a68-c2cb8221f89e",
  "name": "ChoiceBindingSample",
  "kind": "DesktopApp",
  "language": {
    "dialect": "McBasic1",
    "requireExplicit": true,
    "compare": "Binary",
    "defaultArrayBase": 0
  },
  "application": {
    "bundleIdentifier": "com.mcbasic.samples.choicebinding",
    "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"Choice binding — McBasic"
Width660
Height460
MinWidth650
MinHeight440
TypeNameParent
LabellblTitleForm
LabellblStatusForm
LabellblComboForm
ComboBoxcboProductsForm
LabellblListForm
ListBoxlstProductsForm
LabellblSelectedForm
CommandButtoncmdQuitForm
CommandButtoncmdDeleteDraftForm
CommandButtoncmdCancelDraftForm
lblTitle · exact Inspector values
PropertyValue
Left24
Top18
Width540
Height32
TabIndex0
Caption"Choose a product"
Anchor edgesLeft, Top
lblStatus · exact Inspector values
PropertyValue
Left24
Top58
Width540
Height34
TabIndex1
Caption"Opening database..."
Anchor edgesLeft, Top
lblCombo · exact Inspector values
PropertyValue
Left24
Top106
Width220
Height28
TabIndex2
Caption"ComboBox: Name → ID"
Anchor edgesLeft, Top
cboProducts · exact Inspector values
PropertyValue
Left24
Top138
Width280
Height36
TabIndex3
Items""
DisplayMember"Name"
ValueMember"ID"
SelectedIndex-1
Anchor edgesLeft, Top
lblList · exact Inspector values
PropertyValue
Left332
Top106
Width220
Height28
TabIndex4
Caption"ListBox: Category → ID"
Anchor edgesLeft, Top
lstProducts · exact Inspector values
PropertyValue
Left332
Top138
Width280
Height210
TabIndex5
Items""
DisplayMember"Category"
ValueMember"ID"
SelectedIndex-1
Anchor edgesLeft, Top
lblSelected · exact Inspector values
PropertyValue
Left24
Top196
Width280
Height72
TabIndex6
Caption"Select a row in either control."
Anchor edgesLeft, Top
cmdQuit · exact Inspector values
PropertyValue
Left492
Top362
Width120
Height36
TabIndex10
Caption"Quit"
Anchor edgesLeft, Top
cmdDeleteDraft · exact Inspector values
PropertyValue
Left24
Top362
Width150
Height36
Caption"Delete draft row"
TabIndex8
Anchor edgesLeft, Top
cmdCancelDraft · exact Inspector values
PropertyValue
Left186
Top362
Width150
Height36
Caption"Cancel draft"
TabIndex9
Anchor edgesLeft, Top

Step 3Connect the events explicitly#

MainForm

ObjectEventProcedureParameters
cboProductsChangecboProducts_ChangeChange()
lstProductsChange
Alias of SelectionChanged
lstProducts_ChangeChange()
cmdQuitClickcmdQuit_ClickClick()
cmdDeleteDraftClickcmdDeleteDraft_ClickClick()
cmdCancelDraftClickcmdCancelDraft_ClickClick()
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 mItems 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 rows As Recordset
    Dim ignored As Variant

    cmdDeleteDraft.Enabled = False
    cmdCancelDraft.Enabled = False
    Try
        Set mDb = Await Sqlite.OpenAsync(AppPaths.DataFile("choice-binding.db"))
        Set migrations = Sqlite.CreateMigrationSet()
        migrations.Add 1, "CREATE TABLE IF NOT EXISTS Products (ID INTEGER PRIMARY KEY, Name TEXT NOT NULL, Category TEXT NOT NULL)"
        ignored = Await mDb.ApplyMigrationsAsync(migrations)
        Set command = mDb.CreateCommand("INSERT OR IGNORE INTO Products (ID, Name, Category) VALUES (1, 'Apple', 'Fruit'), (2, 'Apricot', 'Fruit'), (3, 'Carrot', 'Vegetable')")
        ignored = Await command.ExecuteAsync()
        Set command = mDb.CreateCommand("SELECT ID, Name, Category FROM Products ORDER BY ID")
        Set rows = Await command.QueryAsync()
        Set mItems = rows.Bind()
        Set cboProducts.DataSource = mItems
        Set lstProducts.DataSource = mItems
        cboProducts.SelectedValue = 2
        lstProducts.SelectedValue = 3
        cmdDeleteDraft.Enabled = True
        lblStatus.Caption = "Name and Category are display columns; ID is the typed value."
        UpdateSelection
    Catch failure As McException
        lblStatus.Caption = "Database error " & CStr(failure.Number) & ": " & failure.Message
    End Try
End Sub
Private Sub cmdDeleteDraft_Click()

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

Forms/MainForm.bas · cmdDeleteDraft_Click
Private Sub cmdDeleteDraft_Click()
    If mItems Is Nothing Then Exit Sub
    If mItems.Dirty Then Exit Sub
    mItems.BeginEdit
    mItems.Delete 0
    cmdDeleteDraft.Enabled = False
    cmdCancelDraft.Enabled = True
    lblStatus.Caption = "Apple removed from the draft only; SQLite is unchanged."
    UpdateSelection
End Sub
Private Sub cmdCancelDraft_Click()

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

Forms/MainForm.bas · cmdCancelDraft_Click
Private Sub cmdCancelDraft_Click()
    If mItems Is Nothing Then Exit Sub
    mItems.CancelEdit
    cmdDeleteDraft.Enabled = True
    cmdCancelDraft.Enabled = False
    lblStatus.Caption = "Draft cancelled; all three rows restored."
    UpdateSelection
End Sub
Private Sub UpdateSelection()

Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.

Forms/MainForm.bas · UpdateSelection
Private Sub UpdateSelection()
    If cboProducts.SelectedIndex >= 0 And lstProducts.SelectedIndex >= 0 Then
        lblSelected.Caption = "Combo ID " & CStr(cboProducts.SelectedValue) & " | List ID " & CStr(lstProducts.SelectedValue)
    Else
        lblSelected.Caption = "Select a row in either control."
    End If
End Sub
Private Sub cboProducts_Change()

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

Forms/MainForm.bas · cboProducts_Change
Private Sub cboProducts_Change()
    UpdateSelection
End Sub
Private Sub lstProducts_Change()

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

Forms/MainForm.bas · lstProducts_Change
Private Sub lstProducts_Change()
    UpdateSelection
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 mItems As BindingSource

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

    cmdDeleteDraft.Enabled = False
    cmdCancelDraft.Enabled = False
    Try
        Set mDb = Await Sqlite.OpenAsync(AppPaths.DataFile("choice-binding.db"))
        Set migrations = Sqlite.CreateMigrationSet()
        migrations.Add 1, "CREATE TABLE IF NOT EXISTS Products (ID INTEGER PRIMARY KEY, Name TEXT NOT NULL, Category TEXT NOT NULL)"
        ignored = Await mDb.ApplyMigrationsAsync(migrations)
        Set command = mDb.CreateCommand("INSERT OR IGNORE INTO Products (ID, Name, Category) VALUES (1, 'Apple', 'Fruit'), (2, 'Apricot', 'Fruit'), (3, 'Carrot', 'Vegetable')")
        ignored = Await command.ExecuteAsync()
        Set command = mDb.CreateCommand("SELECT ID, Name, Category FROM Products ORDER BY ID")
        Set rows = Await command.QueryAsync()
        Set mItems = rows.Bind()
        Set cboProducts.DataSource = mItems
        Set lstProducts.DataSource = mItems
        cboProducts.SelectedValue = 2
        lstProducts.SelectedValue = 3
        cmdDeleteDraft.Enabled = True
        lblStatus.Caption = "Name and Category are display columns; ID is the typed value."
        UpdateSelection
    Catch failure As McException
        lblStatus.Caption = "Database error " & CStr(failure.Number) & ": " & failure.Message
    End Try
End Sub

Private Sub cmdDeleteDraft_Click()
    If mItems Is Nothing Then Exit Sub
    If mItems.Dirty Then Exit Sub
    mItems.BeginEdit
    mItems.Delete 0
    cmdDeleteDraft.Enabled = False
    cmdCancelDraft.Enabled = True
    lblStatus.Caption = "Apple removed from the draft only; SQLite is unchanged."
    UpdateSelection
End Sub

Private Sub cmdCancelDraft_Click()
    If mItems Is Nothing Then Exit Sub
    mItems.CancelEdit
    cmdDeleteDraft.Enabled = True
    cmdCancelDraft.Enabled = False
    lblStatus.Caption = "Draft cancelled; all three rows restored."
    UpdateSelection
End Sub

Private Sub UpdateSelection()
    If cboProducts.SelectedIndex >= 0 And lstProducts.SelectedIndex >= 0 Then
        lblSelected.Caption = "Combo ID " & CStr(cboProducts.SelectedValue) & " | List ID " & CStr(lstProducts.SelectedValue)
    Else
        lblSelected.Caption = "Select a row in either control."
    End If
End Sub

Private Sub cboProducts_Change()
    UpdateSelection
End Sub

Private Sub lstProducts_Change()
    UpdateSelection
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 ChoiceBinding.mcproj
mcbasic run ChoiceBinding.mcproj

Acceptance checklist

  • Confirm ComboBox initially selects ID 2 and ListBox ID 3.
  • Delete the draft row and inspect the visible choices.
  • Cancel the draft: all three records return.
  • Restart the app: the database still contains the seeded records.

Step 7Make one change and repeat the tests#

Add another display column without changing the ValueMember used for identity.

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