McBasic™Get McBasic™ ↗
Build 881
Practical guide

Design control arrays & shared events

Build repeated controls with one name, address a particular member and handle its events without duplicating procedures.

A name identifies the group; Index identifies the member#

A control array contains same-type visual controls with one Name and distinct Index values. Set the group name to txtCell, not txtCell(0); put the number in the separate Index field. The Designer and project tree display the combined member identity. A plain BASIC array of TextBox references is different: it points to existing controls and does not create any controls.

RuleMeaning
IndexAn integer from 0 to 32767, unique within this named array.
TypeEvery member has the same standard visual control type.
MembershipEvery control sharing that name has an Index; even a one-member array needs indexed access.
GapsAllowed. Indexes 2, 5 and 9 describe three members, not ten.
ParentMembers may have different containers on the same Form.

Choose a control whose metadata declares the event you need. Label, Image and Panel now support Click; array membership adds Index to the existing event signature.

Create the grid before writing its behavior#

Select one eligible, non-array visual control on a Form. Choose Design → Create Control Array…, the matching Arrange command in the Design ribbon, or the control’s context-menu command. The selected control is the template and becomes the first member; it is not left behind as a separate control.

FieldValue / effect
Columns1–32 members per row.
Rows1–32 rows.
Spacing0–1000 points between adjacent members.
Group every0 disables grouping; otherwise adds a wider gap after each group of rows and columns.
Group spacing0–1000 extra points at group boundaries.
Start IndexFirst member number; later members count across each row, then down. The final number must not exceed 32767.

Members inherit template properties and advance its TabIndex. The operation must leave the Form with no more than 2048 controls and components. Invalid values leave the project unchanged. Existing bound event headers receive Index as their first parameter in the same Undo step; a direct call to an old handler or other now-invalid source can prevent creation. Resolve the reported code issue first.

Practice with the complete six-cell board lesson, including exact dimensions, all event code and runtime checks.

One event procedure, one incoming member number#

Complete handler; requires txtCell array and lblStatus
Private Sub txtCell_Change(ByVal Index As Integer)
    lblStatus.Caption = "Cell " & Index & ": " & txtCell(Index).Text
End Sub

Index comes before the event’s existing parameters. Use Integer or Long, passed by value; a bare Index parameter is accepted for a synchronous array handler. Async handlers require explicit ByVal Index As Integer and an event that already supports async. They are not a way to make arbitrary parameterized events asynchronous.

Complete shared KeyPress handler; preserves the event’s ByRef flag
Private Sub txtCell_KeyPress(ByVal Index As Integer, ByVal Character As String, ByRef Handled As Boolean)
    If txtCell(Index).Locked Then Handled = True
End Sub

For ListView ItemClick and ItemDoubleClick, distinguish the leading control-array Index from the event’s own item position, conventionally named ItemIndex. Double-clicking a member opens its shared default handler. The component pages show the ordinary single-control signature; an array handler adds Index before it.

Read members, not assumed positions#

BASIC expressionContract
txtCell(i) or txtCell.Item(i)The member whose Index is i; absent members raise error 340.
txtCell.CountThe number of existing members, including hidden ones.
txtCell.LBound, txtCell.UBoundLowest and highest existing Index, not proof that intermediate members exist.
LBound(txtCell), UBound(txtCell)Intrinsic bound forms are also supported.
For Each cell In txtCellVisits existing members in increasing Index order; declare cell As TextBox or As Control.

Prefer For Each when gaps are possible. A counted loop from LBound to UBound can ask for a deleted member. Hiding a member changes Visible, not membership or Count. Programmatic Text changes also raise Change; guard bulk updates with a Boolean flag as the lesson demonstrates.

Understand promotion, paste, rename and Undo#

ActionWhat to expect
Paste or duplicate a matching single controlYes promotes the existing control and the copy to an array and adapts event headers; No gives the copy a new name without changing code.
Paste a member of an existing arrayThe copy receives the next free Index and retains the shared event binding.
Rename a control to an existing same-type nameA confirmation can join or create the array. No leaves the name unchanged.
Rename an array memberChoose Rename Array, Take Out of Array or Cancel. Taking out one member removes its Index and event bindings.
Set Index directly in PropertiesMembership changes, but handler source is not rewritten for you. Correct the signature and run Check.
Delete a memberOnly that member is removed; remaining Index values are not renumbered.

Promotion and joining are checked before applying changes. If a message begins with MCB4603, read the explanation of the code that must change; do not repeatedly paste Yes into an already failing project. A successful Create, paste promotion or rename is one Undo step covering the related layout, headers and target updates.

Point services at a member#

ContextMenu and ValidationProvider Target values select a specific member, for example txtCell(4). The bare group name is not a valid Target. Promotion of a plain control updates a matching Target to its first member. Deleting a targeted control leaves the ContextMenu items but clears its Target; choose a surviving member before Check or Run.

Create and remove a runtime member#

From build 843, Load txtCell(6) creates a member from the saved design-time template with the lowest Index. It begins hidden; set position and state before Visible True. The owning Form must perform Load with an unused Index. Unload removes runtime-created members only. The Modern Workbench includes both buttons, guards, bindings and complete handlers.

ErrorCause / recovery
340Missing or invalid Index; address an existing member.
360Index already loaded; choose an unused Index.
362Cannot unload a design-time member; hide it instead.
365Cannot unload a member during its own event; defer or use another control.

Count and bounds reflect membership. For Each over a dynamically loaded group uses a snapshot at loop entry. Adding a member does not extend that iteration. A removed control is no longer usable.

Give the AI the intended array boundary#

Right-click a member and choose Add Array to Chat when the assistant must edit the group and its shared event procedures. Ordinary Add to Chat on one member does not grant write access to a handler shared by the others. Adding a new Index remains outside the existing array scope and requires permission.

The array chip follows the array name, not a permanent group ID. After renaming the array, add it to scope again and review the chips before sending. AI grid generation uses one addControlArray operation, but still obeys member limits and requires complete handler code. See scope and permissions.

Source baseline 20f4bfed6b · Build manifest
Sources & maintenance

Original web explanation, reviewed against these repository sources. API tables and example files follow the implementation.

  • docs/manual/05-designing-forms.md
  • docs/manual/06-writing-mcbasic.md
  • docs/manual/21-language-reference.md
  • src/McBasic.Workspace/ControlArrayRules.cs
  • src/McBasic.Runtime.UI/McControlArray.cs
  • macos/McBasicIDE/Sources/ControlArrays.swift
  • macos/McBasicIDE/Sources/CreateControlArraySheet.swift
  • tests/McBasic.Compiler.Tests/ControlArrayLoadChecks.cs