Build a menu-driven, dynamic Classic workbench
Rebuild a Classic application with Menu Editor actions, runtime control arrays, control dragging, Type value copies, class initialization and Sub Main.
Create the layout below first. In Design → Menu Editor, add Actions as a title; indent Show details and Clear status underneath it. Name them mnuActions, mnuDetails and mnuClear; set Show details Checked True and Shortcut Ctrl+D. Parent values in the JSON are item IDs, not BASIC names. Give txtCell Index 0 and Dock None: it is the design-time template. DragMode 1 on lblToken is the automatic control-drag source; pnlDrop uses the Source As Control overload, not DragData. Project Settings selects Classic; Startup Object selects Sub Main. Add modStart, SessionCounter and WorkbenchTests before replacing their full source files below. The Boolean mLoaded prevents duplicate Load and missing Unload. Only the newly loaded member is removed; the original template remains. The record test distinguishes copied values (79) from shared identity, while two class instances keep separate counters.
Step 1Prepare a safe practice project#
Extract the archive into a new writable folder, separate from your own application. Open ModernWorkbench.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": "mcbasic-web-modern-workbench",
"name": "ModernWorkbench",
"kind": "DesktopApp",
"language": {
"dialect": "McBasic1",
"requireExplicit": true,
"compare": "Binary",
"defaultArrayBase": 0
},
"application": {
"bundleIdentifier": "com.mcbasic.web.modernworkbench",
"version": "1.0.0",
"buildNumber": "1",
"startupObject": "SubMain",
"shutdownMode": "OnLastFormClosed",
"appearance": "Classic",
"company": "Learning project",
"copyright": "Tutorial example"
},
"forms": [
{
"id": "main",
"typeName": "frmMain",
"layout": "frmMain.form.json",
"code": "frmMain.bas"
}
],
"modules": [
{
"name": "modStart",
"path": "modStart.bas"
}
],
"classes": [
{
"name": "SessionCounter",
"path": "SessionCounter.cls"
}
],
"tests": [
{
"name": "WorkbenchTests",
"path": "WorkbenchTests.bas",
"kind": "unit"
}
],
"references": [],
"resources": [],
"targets": [
"osx-arm64"
],
"capabilities": []
}Step 2Build the Form and set its properties#
frmMain
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 | "Modern Workbench" |
Width | 640 |
Height | 420 |
BackColor | "ButtonFace" |
| Type | Name | Parent |
|---|---|---|
| TextBox | txtCell | Form |
| CommandButton | cmdAdd | Form |
| CommandButton | cmdRemove | Form |
| CommandButton | cmdValue | Form |
| CommandButton | cmdPopup | Form |
| Label | lblToken | Form |
| Panel | pnlDrop | Form |
| Label | lblStatus | Form |
| MenuItem | mnuActions | Component tray |
| MenuItem | mnuDetails | Component tray |
| MenuItem | mnuClear | Component tray |
txtCell · exact Inspector values
| Property | Value |
|---|---|
Index | 0 |
Left | 24 |
Top | 24 |
Width | 160 |
Height | 28 |
Text | "Template" |
Dock | "None" |
cmdAdd · exact Inspector values
| Property | Value |
|---|---|
Left | 24 |
Top | 110 |
Width | 130 |
Height | 32 |
Caption | "Load cell 1" |
cmdRemove · exact Inspector values
| Property | Value |
|---|---|
Left | 164 |
Top | 110 |
Width | 130 |
Height | 32 |
Caption | "Unload cell 1" |
cmdValue · exact Inspector values
| Property | Value |
|---|---|
Left | 304 |
Top | 110 |
Width | 130 |
Height | 32 |
Caption | "Test value copy" |
cmdPopup · exact Inspector values
| Property | Value |
|---|---|
Left | 444 |
Top | 110 |
Width | 160 |
Height | 32 |
Caption | "Open action menu" |
lblToken · exact Inspector values
| Property | Value |
|---|---|
Left | 24 |
Top | 180 |
Width | 160 |
Height | 40 |
Caption | "Drag this label" |
DragMode | 1 |
pnlDrop · exact Inspector values
| Property | Value |
|---|---|
Left | 220 |
Top | 170 |
Width | 380 |
Height | 90 |
BackColor | "WindowBackground" |
lblStatus · exact Inspector values
| Property | Value |
|---|---|
Left | 24 |
Top | 290 |
Width | 580 |
Height | 70 |
Caption | "Ready" |
WordWrap | True |
mnuActions · exact Inspector values
| Property | Value |
|---|---|
Caption | "&Actions" |
Order | 0 |
mnuDetails · exact Inspector values
| Property | Value |
|---|---|
Caption | "&Show details" |
Parent | "actions" |
Order | 0 |
Shortcut | "Ctrl+D" |
Checked | True |
mnuClear · exact Inspector values
| Property | Value |
|---|---|
Caption | "&Clear status" |
Parent | "actions" |
Order | 1 |
Step 3Connect the events explicitly#
frmMain
| Object | Event | Procedure | Parameters |
|---|---|---|---|
txtCell | Change | txtCell_Change | Change(ByVal Index As Integer) |
cmdAdd | Click | cmdAdd_Click | Click() |
cmdRemove | Click | cmdRemove_Click | Click() |
cmdValue | Click | cmdValue_Click | Click() |
cmdPopup | Click | cmdPopup_Click | Click() |
pnlDrop | DragOver | pnlDrop_DragOver | DragOver(ByVal Source As Control, ByVal X As Single, ByVal Y As Single, ByVal State As Integer) |
pnlDrop | DragDrop | pnlDrop_DragDrop | DragDrop(ByVal Source As Control, ByVal X As Single, ByVal Y As Single) |
mnuDetails | Click | mnuDetails_Click | Click() |
mnuClear | Click | mnuClear_Click | Click() |
| Form | Load | Form_Load | () |
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.
frmMain.bas
Enter these declarations at file scope, before the procedures.
Option Explicit
Private mLoaded As Boolean
Private mSession As SessionCounterPrivate Sub Form_Load()
Initialize state before enabling dependent actions. Keep the shared declarations and startup dependencies with this procedure.
Private Sub Form_Load()
Set mSession = New SessionCounter
mLoaded = False
cmdRemove.Enabled = False
lblStatus.Caption = "Ready; " & txtCell.Count & " cell. " & App.CompanyName
End SubPrivate Sub cmdAdd_Click()
Enter the complete action, including its input checks and calls to helper procedures.
Private Sub cmdAdd_Click()
If mLoaded Then Exit Sub
Load txtCell(1)
mLoaded = True
txtCell(1).Top = 64
txtCell(1).Text = "New member"
txtCell(1).Visible = True
cmdAdd.Enabled = False
cmdRemove.Enabled = True
lblStatus.Caption = "Loaded; " & txtCell.Count & " cells."
End SubPrivate Sub cmdRemove_Click()
Enter the complete action, including its input checks and calls to helper procedures.
Private Sub cmdRemove_Click()
If Not mLoaded Then Exit Sub
Unload txtCell(1)
mLoaded = False
cmdAdd.Enabled = True
cmdRemove.Enabled = False
lblStatus.Caption = "Removed; " & txtCell.Count & " cell remains."
End SubPrivate Sub txtCell_Change(ByVal Index As Integer)
Read the current value or selection and update its dependent interface. The binding above determines which object calls this handler.
Private Sub txtCell_Change(ByVal Index As Integer)
lblStatus.Caption = "Cell " & Index & ": " & txtCell(Index).Text
End SubPrivate Sub cmdValue_Click()
Enter the complete action, including its input checks and calls to helper procedures.
Private Sub cmdValue_Click()
mSession.Increment
lblStatus.Caption = "Copy proof: " & CopyProof() & "; clicks: " & mSession.Count
End SubPrivate Sub cmdPopup_Click()
Enter the complete action, including its input checks and calls to helper procedures.
Private Sub cmdPopup_Click()
PopupMenu mnuActions
End SubPrivate Sub mnuDetails_Click()
Enter the complete action, including its input checks and calls to helper procedures.
Private Sub mnuDetails_Click()
mnuDetails.Checked = Not mnuDetails.Checked
lblStatus.Visible = mnuDetails.Checked
End SubPrivate Sub mnuClear_Click()
Enter the complete action, including its input checks and calls to helper procedures.
Private Sub mnuClear_Click()
lblStatus.Caption = "Ready"
End SubPrivate Sub pnlDrop_DragOver(ByVal Source As Control, ByVal X As Single, ByVal Y As Single, ByVal State As Integer)
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Private Sub pnlDrop_DragOver(ByVal Source As Control, ByVal X As Single, ByVal Y As Single, ByVal State As Integer)
If State = vbEnter Then lblStatus.Caption = "Release to identify " & Source.Name
End SubPrivate Sub pnlDrop_DragDrop(ByVal Source As Control, ByVal X As Single, ByVal Y As Single)
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Private Sub pnlDrop_DragDrop(ByVal Source As Control, ByVal X As Single, ByVal Y As Single)
lblStatus.Caption = "Dropped " & Source.Name & " at " & X & ", " & Y
End SubCopy the complete frmMain.bas file
Replace the file once; do not append duplicate procedures.
Option Explicit
Private mLoaded As Boolean
Private mSession As SessionCounter
Private Sub Form_Load()
Set mSession = New SessionCounter
mLoaded = False
cmdRemove.Enabled = False
lblStatus.Caption = "Ready; " & txtCell.Count & " cell. " & App.CompanyName
End Sub
Private Sub cmdAdd_Click()
If mLoaded Then Exit Sub
Load txtCell(1)
mLoaded = True
txtCell(1).Top = 64
txtCell(1).Text = "New member"
txtCell(1).Visible = True
cmdAdd.Enabled = False
cmdRemove.Enabled = True
lblStatus.Caption = "Loaded; " & txtCell.Count & " cells."
End Sub
Private Sub cmdRemove_Click()
If Not mLoaded Then Exit Sub
Unload txtCell(1)
mLoaded = False
cmdAdd.Enabled = True
cmdRemove.Enabled = False
lblStatus.Caption = "Removed; " & txtCell.Count & " cell remains."
End Sub
Private Sub txtCell_Change(ByVal Index As Integer)
lblStatus.Caption = "Cell " & Index & ": " & txtCell(Index).Text
End Sub
Private Sub cmdValue_Click()
mSession.Increment
lblStatus.Caption = "Copy proof: " & CopyProof() & "; clicks: " & mSession.Count
End Sub
Private Sub cmdPopup_Click()
PopupMenu mnuActions
End Sub
Private Sub mnuDetails_Click()
mnuDetails.Checked = Not mnuDetails.Checked
lblStatus.Visible = mnuDetails.Checked
End Sub
Private Sub mnuClear_Click()
lblStatus.Caption = "Ready"
End Sub
Private Sub pnlDrop_DragOver(ByVal Source As Control, ByVal X As Single, ByVal Y As Single, ByVal State As Integer)
If State = vbEnter Then lblStatus.Caption = "Release to identify " & Source.Name
End Sub
Private Sub pnlDrop_DragDrop(ByVal Source As Control, ByVal X As Single, ByVal Y As Single)
lblStatus.Caption = "Dropped " & Source.Name & " at " & X & ", " & Y
End SubmodStart.bas
Enter these declarations at file scope, before the procedures.
Option Explicit
Public Type Reading
Name As String
Value As Long
End TypePublic Sub Main()
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Sub Main()
frmMain.Show
End SubPublic Function CopyProof() As Long
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Function CopyProof() As Long
Dim original As Reading
Dim edited As Reading
original.Name = "Sample"
original.Value = 7
edited = original
With edited
.Value = 9
End With
CopyProof = original.Value * 10 + edited.Value
End FunctionCopy the complete modStart.bas file
Replace the file once; do not append duplicate procedures.
Option Explicit
Public Type Reading
Name As String
Value As Long
End Type
Public Sub Main()
frmMain.Show
End Sub
Public Function CopyProof() As Long
Dim original As Reading
Dim edited As Reading
original.Name = "Sample"
original.Value = 7
edited = original
With edited
.Value = 9
End With
CopyProof = original.Value * 10 + edited.Value
End FunctionSessionCounter.cls
Enter these declarations at file scope, before the procedures.
Option Explicit
Private mCount As LongPrivate Sub Class_Initialize()
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Private Sub Class_Initialize()
mCount = 0
End SubPublic Sub Increment()
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Sub Increment()
mCount = mCount + 1
End SubPublic Property Get Count() As Long
Keep this public property accessor with its complementary accessor and the fields or controls it uses.
Public Property Get Count() As Long
Count = mCount
End PropertyCopy the complete SessionCounter.cls file
Replace the file once; do not append duplicate procedures.
Option Explicit
Private mCount As Long
Private Sub Class_Initialize()
mCount = 0
End Sub
Public Sub Increment()
mCount = mCount + 1
End Sub
Public Property Get Count() As Long
Count = mCount
End PropertyWorkbenchTests.bas
Enter these declarations at file scope, before the procedures.
Option ExplicitPublic Sub Test_ValueCopy()
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Sub Test_ValueCopy()
Assert.AreEqual 79, CopyProof()
End SubPublic Sub Test_IndependentClasses()
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Sub Test_IndependentClasses()
Dim first As SessionCounter
Dim second As SessionCounter
Set first = New SessionCounter
Set second = New SessionCounter
first.Increment
Assert.AreEqual 1, first.Count
Assert.AreEqual 0, second.Count
End SubCopy the complete WorkbenchTests.bas file
Replace the file once; do not append duplicate procedures.
Option Explicit
Public Sub Test_ValueCopy()
Assert.AreEqual 79, CopyProof()
End Sub
Public Sub Test_IndependentClasses()
Dim first As SessionCounter
Dim second As SessionCounter
Set first = New SessionCounter
Set second = New SessionCounter
first.Increment
Assert.AreEqual 1, first.Count
Assert.AreEqual 0, second.Count
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 ModernWorkbench.mcproj
mcbasic run ModernWorkbench.mcproj
mcbasic test ModernWorkbench.mcprojAcceptance checklist
- Check the complete project, then run Test: both unit tests pass.
- Run: the startup procedure opens one Classic window; the initial label reports one cell.
- Load cell 1: a second field becomes visible below the template. Typing in it reports Cell 1. Add is disabled.
- Unload cell 1: it disappears, Count returns to one, and Load becomes available again. Repeat twice.
- Test value copy twice: the proof stays 79 and the class counter advances from 1 to 2.
- Use Actions → Show details and its shortcut: the checkmark and label visibility agree. Restore visibility, then test the popup button and Clear status.
- Drag the label onto the white panel: expect Dropped lblToken with coordinates. The label itself intentionally does not move.
- Close the window: the Sub Main application ends. Change Appearance to macOS and repeat the interaction checks.
Step 7Make one change and repeat the tests#
Add another menu command that calls the existing copy-proof helper. Do not call an event handler directly; share the calculation and preserve the original two tests.
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 manifestSources & maintenance
Original web explanation, reviewed against these repository sources. API tables and example files follow the implementation.
tests/McBasic.Compiler.Tests/UserTypeChecks.cstests/McBasic.Compiler.Tests/Vb6ScreenClassChecks.cstests/McBasic.Compiler.Tests/SubMainChecks.cstests/McBasic.Compiler.Tests/ApplicationObjectChecks.cssrc/McBasic.Runtime/McApp.cssrc/McBasic.Compiler/Binder.cssrc/McBasic.Workspace/MenuRules.cssrc/McBasic.Workspace/MenuShortcuts.csmacos/McBasicIDE/Sources/MenuEditorWindow.swifttests/McBasic.Compiler.Tests/MenuCompilerChecks.cstests/McBasic.Compiler.Tests/PopupMenuChecks.csmacos/McBasicIDE/Sources/ProjectSettingsSheet.swiftsrc/McBasic.Workspace/ProjectReader.cstests/McBasic.Compiler.Tests/ApplicationAppearanceChecks.cstests/McBasic.Compiler.Tests/AppIconChecks.cstests/McBasic.Compiler.Tests/VbControlDragChecks.cssrc/McBasic.Runtime/McSystemColors.cssrc/McBasic.Runtime.UI/McControlArray.cssite/examples/ModernWorkbench/ModernWorkbench.mcprojsite/examples/ModernWorkbench/SessionCounter.clssite/examples/ModernWorkbench/WorkbenchTests.bassite/examples/ModernWorkbench/frmMain.bassite/examples/ModernWorkbench/frmMain.form.jsonsite/examples/ModernWorkbench/modStart.bas