Add MainMenu, PopupMenu, MenuItem, and RadioGroup control types

Menus use 0 0 0 0 geometry (non-visual) with Parent property for
hierarchy. TFormCtrlRec.Control widened from TControl to TComponent
to support non-visual menu components. RadioGroup auto-wires Click
with ItemIndex data.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Duensing 2026-03-05 14:40:31 -06:00
parent 2d5ed2a3b1
commit 03d44440fd
4 changed files with 362 additions and 108 deletions

View file

@ -322,6 +322,10 @@ messages are available, dispatching each command as it arrives.
| `Panel` | TPanel | Cosmetic container panel | | `Panel` | TPanel | Cosmetic container panel |
| `ScrollBar` | TScrollBar | Horizontal or vertical scrollbar| | `ScrollBar` | TScrollBar | Horizontal or vertical scrollbar|
| `MediaPlayer` | TMediaPlayer | MCI media player control | | `MediaPlayer` | TMediaPlayer | MCI media player control |
| `MainMenu` | TMainMenu | Form main menu bar |
| `PopupMenu` | TPopupMenu | Context (right-click) menu |
| `MenuItem` | TMenuItem | Menu item (child of menu) |
| `RadioGroup` | TRadioGroup | Grouped radio buttons |
### Creating Controls ### Creating Controls
@ -359,12 +363,12 @@ CTRL.SET 1 3 Text="world" Enabled=0
### Caption ### Caption
- **Applies to:** Label, Button, CheckBox, GroupBox, RadioButton, Panel - **Applies to:** Label, Button, CheckBox, GroupBox, RadioButton, Panel, MenuItem, RadioGroup
- **Format:** Quoted string - **Format:** Quoted string
- **Example:** `Caption="Submit"` - **Example:** `Caption="Submit"`
The display text for labels, buttons, check boxes, group boxes, radio The display text for labels, buttons, check boxes, group boxes, radio
buttons, and panels. buttons, panels, menu items, and radio groups.
### Text ### Text
@ -381,7 +385,7 @@ CTRL.SET 1 5 Text="Line one\nLine two\nLine three"
### Items ### Items
- **Applies to:** ListBox, ComboBox - **Applies to:** ListBox, ComboBox, RadioGroup
- **Format:** Quoted string, items separated by `\n` - **Format:** Quoted string, items separated by `\n`
- **Example:** `Items="Red\nGreen\nBlue"` - **Example:** `Items="Red\nGreen\nBlue"`
@ -390,7 +394,7 @@ new items are added.
### Checked ### Checked
- **Applies to:** CheckBox, RadioButton - **Applies to:** CheckBox, RadioButton, MenuItem
- **Format:** `0` (unchecked) or `1` (checked) - **Format:** `0` (unchecked) or `1` (checked)
- **Example:** `Checked=1` - **Example:** `Checked=1`
@ -433,7 +437,7 @@ Maximum number of characters the user can type.
### ItemIndex ### ItemIndex
- **Applies to:** ListBox, ComboBox - **Applies to:** ListBox, ComboBox, RadioGroup
- **Format:** Integer (-1 = no selection) - **Format:** Integer (-1 = no selection)
- **Example:** `ItemIndex=2` - **Example:** `ItemIndex=2`
@ -588,6 +592,41 @@ Pseudo-property that triggers a method call instead of setting a
value. Valid commands: `Open`, `Play`, `Stop`, `Close`, `Pause`, value. Valid commands: `Open`, `Play`, `Stop`, `Close`, `Pause`,
`Resume`, `Rewind`, `Next`, `Previous`. `Resume`, `Rewind`, `Next`, `Previous`.
### Parent
- **Applies to:** MenuItem
- **Format:** Integer (ctrlId of parent menu or menu item)
- **Example:** `Parent=1`
Specifies the parent for a menu item. The parent can be a MainMenu,
PopupMenu, or another MenuItem (for submenus).
### Columns
- **Applies to:** RadioGroup
- **Format:** Integer
- **Example:** `Columns=2`
Number of columns for the radio button layout.
### ShortCut
- **Applies to:** MenuItem
- **Format:** Integer (Delphi ShortCut value)
- **Example:** `ShortCut=16467`
Keyboard accelerator for the menu item. Uses Delphi's ShortCut
encoding (virtual key + modifier flags).
### PopupMenu
- **Applies to:** Any visual control
- **Format:** Integer (ctrlId of a PopupMenu)
- **Example:** `PopupMenu=2`
Associates a PopupMenu with a control. When the user right-clicks
the control, the popup menu is displayed.
### BasePath ### BasePath
`BasePath` is a property on `TFormClient` (not a protocol property). `BasePath` is a property on `TFormClient` (not a protocol property).
@ -619,6 +658,8 @@ No `EVENT.BIND` command is needed.
| ListBox | Select | `<index> "selected text"` | | ListBox | Select | `<index> "selected text"` |
| ComboBox | Select | `<index> "selected text"` | | ComboBox | Select | `<index> "selected text"` |
| ComboBox | Change | `"new text"` | | ComboBox | Change | `"new text"` |
| MenuItem | Click | (none) |
| RadioGroup | Click | `<itemIndex>` |
### Opt-in Events ### Opt-in Events
@ -666,6 +707,25 @@ EVENT 1 3 KeyDown 13 (client sends back when Enter pressed)
EVENT.UNBIND 1 3 KeyDown (server disconnects) EVENT.UNBIND 1 3 KeyDown (server disconnects)
``` ```
### Menu Hierarchy Example
Menus are built using flat CTRL.CREATE commands with `Parent` properties
to establish the hierarchy:
```
CTRL.CREATE 1 1 MainMenu 0 0 0 0
CTRL.CREATE 1 2 MenuItem 0 0 0 0 Caption="&File" Parent=1
CTRL.CREATE 1 3 MenuItem 0 0 0 0 Caption="&Open" Parent=2 ShortCut=16463
CTRL.CREATE 1 4 MenuItem 0 0 0 0 Caption="&Save" Parent=2 ShortCut=16467
CTRL.CREATE 1 5 MenuItem 0 0 0 0 Caption="E&xit" Parent=2
CTRL.CREATE 1 6 MenuItem 0 0 0 0 Caption="&Help" Parent=1
CTRL.CREATE 1 7 MenuItem 0 0 0 0 Caption="&About" Parent=6
```
This creates a menu bar with File (Open, Save, Exit) and Help (About).
MenuItem ctrl 2 is a child of MainMenu ctrl 1; ctrl 3-5 are children of
the File item (ctrl 2); ctrl 7 is a child of the Help item (ctrl 6).
## String Encoding ## String Encoding
All strings in the protocol are double-quoted. The following escape All strings in the protocol are double-quoted. The following escape
@ -697,9 +757,8 @@ any protocol or application code.
## Limitations ## Limitations
- **RadioButton grouping:** All RadioButtons on a form belong to one - **RadioButton grouping:** All RadioButtons on a form belong to one
group. There is no support for multiple independent radio groups group. Use RadioGroup for multiple independent radio groups per
per form (the protocol has a flat parent model with no child form.
containment).
- **Image format:** Only BMP files are supported for the Picture - **Image format:** Only BMP files are supported for the Picture
property. property.
- **dfm2form and Picture:** The converter cannot extract image - **dfm2form and Picture:** The converter cannot extract image

View file

@ -32,7 +32,11 @@ typedef enum {
ctRadioButton, ctRadioButton,
ctPanel, ctPanel,
ctScrollBar, ctScrollBar,
ctMediaPlayer ctMediaPlayer,
ctMainMenu,
ctPopupMenu,
ctMenuItem,
ctRadioGroup
} CtrlTypeE; } CtrlTypeE;
typedef struct { typedef struct {
@ -103,6 +107,11 @@ typedef struct {
bool hasAutoOpen; bool hasAutoOpen;
bool hasFileName; bool hasFileName;
bool hasDeviceType; bool hasDeviceType;
int32_t parentCtrlIdx;
int32_t columns;
int32_t shortCut;
bool hasColumns;
bool hasShortCut;
} DfmCtrlT; } DfmCtrlT;
typedef struct { typedef struct {
@ -143,7 +152,7 @@ static void escapeStr(const char *src, char *dst, int32_t dstSize);
static void initCtrl(DfmCtrlT *ctrl); static void initCtrl(DfmCtrlT *ctrl);
static void initForm(DfmFormT *form); static void initForm(DfmFormT *form);
static CtrlTypeE mapClassName(const char *className); static CtrlTypeE mapClassName(const char *className);
static bool parseComponent(const uint8_t *data, int32_t size, int32_t *pos, DfmFormT *form, bool isRoot); static bool parseComponent(const uint8_t *data, int32_t size, int32_t *pos, DfmFormT *form, bool isRoot, int32_t parentIdx);
static void parseProperties(const uint8_t *data, int32_t size, int32_t *pos, DfmFormT *form, DfmCtrlT *ctrl, bool isForm); static void parseProperties(const uint8_t *data, int32_t size, int32_t *pos, DfmFormT *form, DfmCtrlT *ctrl, bool isForm);
static int32_t readByte(const uint8_t *data, int32_t size, int32_t *pos); static int32_t readByte(const uint8_t *data, int32_t size, int32_t *pos);
static int32_t readInt16LE(const uint8_t *data, int32_t size, int32_t *pos); static int32_t readInt16LE(const uint8_t *data, int32_t size, int32_t *pos);
@ -360,6 +369,18 @@ static CtrlTypeE mapClassName(const char *className) {
if (stricmp_local(className, "TMediaPlayer") == 0) { if (stricmp_local(className, "TMediaPlayer") == 0) {
return ctMediaPlayer; return ctMediaPlayer;
} }
if (stricmp_local(className, "TMainMenu") == 0) {
return ctMainMenu;
}
if (stricmp_local(className, "TPopupMenu") == 0) {
return ctPopupMenu;
}
if (stricmp_local(className, "TMenuItem") == 0) {
return ctMenuItem;
}
if (stricmp_local(className, "TRadioGroup") == 0) {
return ctRadioGroup;
}
return ctUnknown; return ctUnknown;
} }
@ -377,6 +398,7 @@ static void initCtrl(DfmCtrlT *ctrl) {
ctrl->bevelOuter = 1; // bvRaised ctrl->bevelOuter = 1; // bvRaised
ctrl->largeChange = 1; ctrl->largeChange = 1;
ctrl->smallChange = 1; ctrl->smallChange = 1;
ctrl->parentCtrlIdx = -1;
} }
@ -790,6 +812,12 @@ static void parseProperties(const uint8_t *data, int32_t size, int32_t *pos, Dfm
ctrl->autoOpen = readIntValue(data, size, pos, tag); ctrl->autoOpen = readIntValue(data, size, pos, tag);
} }
ctrl->hasAutoOpen = true; ctrl->hasAutoOpen = true;
} else if (!isForm && stricmp_local(propName, "Columns") == 0) {
ctrl->columns = readIntValue(data, size, pos, tag);
ctrl->hasColumns = true;
} else if (!isForm && stricmp_local(propName, "ShortCut") == 0) {
ctrl->shortCut = readIntValue(data, size, pos, tag);
ctrl->hasShortCut = true;
} else { } else {
skipValue(data, size, pos, tag); skipValue(data, size, pos, tag);
} }
@ -801,7 +829,7 @@ static void parseProperties(const uint8_t *data, int32_t size, int32_t *pos, Dfm
// Parse a component (form or child control) recursively // Parse a component (form or child control) recursively
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
static bool parseComponent(const uint8_t *data, int32_t size, int32_t *pos, DfmFormT *form, bool isRoot) { static bool parseComponent(const uint8_t *data, int32_t size, int32_t *pos, DfmFormT *form, bool isRoot, int32_t parentIdx) {
// Check for flags byte (Delphi 1.0 rarely uses this but handle it) // Check for flags byte (Delphi 1.0 rarely uses this but handle it)
uint8_t peek = data[*pos]; uint8_t peek = data[*pos];
if ((peek & 0xF0) == 0xF0) { if ((peek & 0xF0) == 0xF0) {
@ -833,7 +861,7 @@ static bool parseComponent(const uint8_t *data, int32_t size, int32_t *pos, DfmF
(*pos)++; // consume terminator (*pos)++; // consume terminator
break; break;
} }
parseComponent(data, size, pos, form, false); parseComponent(data, size, pos, form, false, -1);
} }
} else { } else {
// Child control // Child control
@ -851,7 +879,7 @@ static bool parseComponent(const uint8_t *data, int32_t size, int32_t *pos, DfmF
(*pos)++; (*pos)++;
break; break;
} }
parseComponent(data, size, pos, form, false); parseComponent(data, size, pos, form, false, -1);
} }
return true; return true;
} }
@ -861,22 +889,24 @@ static bool parseComponent(const uint8_t *data, int32_t size, int32_t *pos, DfmF
exit(1); exit(1);
} }
DfmCtrlT *ctrl = &form->ctrls[form->ctrlCount]; int32_t myIdx = form->ctrlCount;
DfmCtrlT *ctrl = &form->ctrls[myIdx];
initCtrl(ctrl); initCtrl(ctrl);
ctrl->type = type; ctrl->type = type;
ctrl->parentCtrlIdx = parentIdx;
snprintf(ctrl->name, sizeof(ctrl->name), "%s", instName); snprintf(ctrl->name, sizeof(ctrl->name), "%s", instName);
parseProperties(data, size, pos, form, ctrl, false); parseProperties(data, size, pos, form, ctrl, false);
form->ctrlCount++; form->ctrlCount++;
// Skip nested children (controls within controls, e.g., panels) // Recurse into children (menu items, panels, etc.)
while (*pos < size) { while (*pos < size) {
peek = data[*pos]; peek = data[*pos];
if (peek == 0x00) { if (peek == 0x00) {
(*pos)++; (*pos)++;
break; break;
} }
parseComponent(data, size, pos, form, false); parseComponent(data, size, pos, form, false, myIdx);
} }
} }
@ -931,13 +961,16 @@ static void emitCtrl(FILE *out, int32_t formId, int32_t ctrlId, DfmCtrlT *ctrl)
"Unknown", "Label", "Edit", "Button", "Unknown", "Label", "Edit", "Button",
"CheckBox", "ListBox", "ComboBox", "Memo", "CheckBox", "ListBox", "ComboBox", "Memo",
"Image", "GroupBox", "RadioButton", "Panel", "Image", "GroupBox", "RadioButton", "Panel",
"ScrollBar", "MediaPlayer" "ScrollBar", "MediaPlayer",
"MainMenu", "PopupMenu", "MenuItem", "RadioGroup"
}; };
char escaped[8192]; char escaped[8192];
fprintf(out, "CTRL.CREATE %d %d %s %d %d %d %d", if (ctrl->type == ctMainMenu || ctrl->type == ctPopupMenu || ctrl->type == ctMenuItem) {
formId, ctrlId, typeNames[ctrl->type], fprintf(out, "CTRL.CREATE %d %d %s 0 0 0 0", formId, ctrlId, typeNames[ctrl->type]);
ctrl->left, ctrl->top, ctrl->width, ctrl->height); } else {
fprintf(out, "CTRL.CREATE %d %d %s %d %d %d %d", formId, ctrlId, typeNames[ctrl->type], ctrl->left, ctrl->top, ctrl->width, ctrl->height);
}
// Inline properties // Inline properties
if (ctrl->hasCaption) { if (ctrl->hasCaption) {
@ -1023,6 +1056,15 @@ static void emitCtrl(FILE *out, int32_t formId, int32_t ctrlId, DfmCtrlT *ctrl)
if (ctrl->hasAutoOpen && ctrl->autoOpen) { if (ctrl->hasAutoOpen && ctrl->autoOpen) {
fprintf(out, " AutoOpen=1"); fprintf(out, " AutoOpen=1");
} }
if (ctrl->type == ctMenuItem && ctrl->parentCtrlIdx >= 0) {
fprintf(out, " Parent=%d", ctrl->parentCtrlIdx + 1);
}
if (ctrl->hasColumns && ctrl->columns != 0) {
fprintf(out, " Columns=%d", ctrl->columns);
}
if (ctrl->hasShortCut && ctrl->shortCut != 0) {
fprintf(out, " ShortCut=%d", ctrl->shortCut);
}
fprintf(out, "\n"); fprintf(out, "\n");
@ -1030,7 +1072,7 @@ static void emitCtrl(FILE *out, int32_t formId, int32_t ctrlId, DfmCtrlT *ctrl)
// Auto-wired: Button/CheckBox→Click, Edit→Change, ListBox→Select, // Auto-wired: Button/CheckBox→Click, Edit→Change, ListBox→Select,
// ComboBox→Select+Change, Memo→Change // ComboBox→Select+Change, Memo→Change
bool autoClick = (ctrl->type == ctButton || ctrl->type == ctCheckBox || ctrl->type == ctRadioButton); bool autoClick = (ctrl->type == ctButton || ctrl->type == ctCheckBox || ctrl->type == ctRadioButton || ctrl->type == ctMenuItem || ctrl->type == ctRadioGroup);
bool autoChange = (ctrl->type == ctEdit || ctrl->type == ctComboBox || ctrl->type == ctMemo || ctrl->type == ctScrollBar); bool autoChange = (ctrl->type == ctEdit || ctrl->type == ctComboBox || ctrl->type == ctMemo || ctrl->type == ctScrollBar);
bool autoSelect = (ctrl->type == ctListBox || ctrl->type == ctComboBox); bool autoSelect = (ctrl->type == ctListBox || ctrl->type == ctComboBox);
@ -1160,7 +1202,7 @@ int main(int argc, char *argv[]) {
DfmFormT form; DfmFormT form;
initForm(&form); initForm(&form);
int32_t pos = 4; // skip TPF0 int32_t pos = 4; // skip TPF0
parseComponent(data, (int32_t)fileSize, &pos, &form, true); parseComponent(data, (int32_t)fileSize, &pos, &form, true, -1);
// Default caption if empty // Default caption if empty
if (form.caption[0] == '\0') { if (form.caption[0] == '\0') {

View file

@ -13,7 +13,7 @@ unit FormCli;
interface interface
uses uses
SysUtils, Classes, Controls, Forms, StdCtrls, ExtCtrls, MPlayer, WinTypes, WinProcs; SysUtils, Classes, Controls, Forms, StdCtrls, ExtCtrls, Menus, MPlayer, WinTypes, WinProcs;
const const
MaxMsgLen = 4096; MaxMsgLen = 4096;
@ -29,7 +29,8 @@ type
TCtrlTypeE = (ctUnknown, ctLabel, ctEdit, ctButton, TCtrlTypeE = (ctUnknown, ctLabel, ctEdit, ctButton,
ctCheckBox, ctListBox, ctComboBox, ctMemo, ctCheckBox, ctListBox, ctComboBox, ctMemo,
ctImage, ctGroupBox, ctRadioButton, ctPanel, ctImage, ctGroupBox, ctRadioButton, ctPanel,
ctScrollBar, ctMediaPlayer); ctScrollBar, ctMediaPlayer,
ctMainMenu, ctPopupMenu, ctMenuItem, ctRadioGroup);
{ Bound event flags } { Bound event flags }
TBoundEvent = (beDblClick, beKeyDown, beKeyUp, TBoundEvent = (beDblClick, beKeyDown, beKeyUp,
@ -42,7 +43,7 @@ type
TFormCtrlRec = record TFormCtrlRec = record
CtrlId: Integer; CtrlId: Integer;
CtrlType: TCtrlTypeE; CtrlType: TCtrlTypeE;
Control: TControl; Control: TComponent;
Bound: TBoundEvents; Bound: TBoundEvents;
end; end;
@ -81,8 +82,8 @@ type
procedure FreeCtrlRec(CR: PFormCtrlRec); procedure FreeCtrlRec(CR: PFormCtrlRec);
{ Property application } { Property application }
procedure ApplyProp(CR: PFormCtrlRec; Key, Value: PChar); procedure ApplyProp(FR: PFormRec; CR: PFormCtrlRec; Key, Value: PChar);
procedure ApplyInlineProps(CR: PFormCtrlRec; P: PChar); procedure ApplyInlineProps(FR: PFormRec; CR: PFormCtrlRec; P: PChar);
{ Event wiring } { Event wiring }
procedure WireAutoEvents(CR: PFormCtrlRec); procedure WireAutoEvents(CR: PFormCtrlRec);
@ -109,7 +110,9 @@ type
procedure HandleClick(Sender: TObject); procedure HandleClick(Sender: TObject);
procedure HandleRadioButtonClick(Sender: TObject); procedure HandleRadioButtonClick(Sender: TObject);
procedure HandleScrollBarChange(Sender: TObject); procedure HandleScrollBarChange(Sender: TObject);
procedure HandleMenuItemClick(Sender: TObject);
procedure HandleMediaPlayerNotify(Sender: TObject); procedure HandleMediaPlayerNotify(Sender: TObject);
procedure HandleRadioGroupClick(Sender: TObject);
{ Outgoing event helpers } { Outgoing event helpers }
procedure SendEvent(FormId, CtrlId: Integer; const EventName: string; const Data: string); procedure SendEvent(FormId, CtrlId: Integer; const EventName: string; const Data: string);
@ -333,8 +336,12 @@ end;
procedure TFormClient.FreeCtrlRec(CR: PFormCtrlRec); procedure TFormClient.FreeCtrlRec(CR: PFormCtrlRec);
begin begin
{ Menu items are owned by their parent menu and freed automatically }
if CR^.Control <> nil then if CR^.Control <> nil then
begin
if not (CR^.Control is TMenuItem) then
CR^.Control.Free; CR^.Control.Free;
end;
Dispose(CR); Dispose(CR);
end; end;
@ -403,7 +410,7 @@ var
FormId: Integer; FormId: Integer;
CtrlId: Integer; CtrlId: Integer;
begin begin
Tag := (Sender as TControl).Tag; Tag := (Sender as TComponent).Tag;
FormId := Tag shr 16; FormId := Tag shr 16;
CtrlId := Tag and $FFFF; CtrlId := Tag and $FFFF;
SendEvent(FormId, CtrlId, 'Click', ''); SendEvent(FormId, CtrlId, 'Click', '');
@ -416,7 +423,7 @@ var
FormId: Integer; FormId: Integer;
CtrlId: Integer; CtrlId: Integer;
begin begin
Tag := (Sender as TControl).Tag; Tag := (Sender as TComponent).Tag;
FormId := Tag shr 16; FormId := Tag shr 16;
CtrlId := Tag and $FFFF; CtrlId := Tag and $FFFF;
SendEvent(FormId, CtrlId, 'Click', ''); SendEvent(FormId, CtrlId, 'Click', '');
@ -431,7 +438,7 @@ var
Escaped: array[0..4095] of Char; Escaped: array[0..4095] of Char;
Txt: string; Txt: string;
begin begin
Tag := (Sender as TControl).Tag; Tag := (Sender as TComponent).Tag;
FormId := Tag shr 16; FormId := Tag shr 16;
CtrlId := Tag and $FFFF; CtrlId := Tag and $FFFF;
Txt := (Sender as TEdit).Text; Txt := (Sender as TEdit).Text;
@ -449,7 +456,7 @@ var
Escaped: array[0..4095] of Char; Escaped: array[0..4095] of Char;
Txt: string; Txt: string;
begin begin
Tag := (Sender as TControl).Tag; Tag := (Sender as TComponent).Tag;
FormId := Tag shr 16; FormId := Tag shr 16;
CtrlId := Tag and $FFFF; CtrlId := Tag and $FFFF;
Txt := (Sender as TMemo).Text; Txt := (Sender as TMemo).Text;
@ -467,7 +474,7 @@ var
Escaped: array[0..4095] of Char; Escaped: array[0..4095] of Char;
Txt: string; Txt: string;
begin begin
Tag := (Sender as TControl).Tag; Tag := (Sender as TComponent).Tag;
FormId := Tag shr 16; FormId := Tag shr 16;
CtrlId := Tag and $FFFF; CtrlId := Tag and $FFFF;
Txt := (Sender as TComboBox).Text; Txt := (Sender as TComboBox).Text;
@ -486,7 +493,7 @@ var
Escaped: array[0..4095] of Char; Escaped: array[0..4095] of Char;
Txt: string; Txt: string;
begin begin
Tag := (Sender as TControl).Tag; Tag := (Sender as TComponent).Tag;
FormId := Tag shr 16; FormId := Tag shr 16;
CtrlId := Tag and $FFFF; CtrlId := Tag and $FFFF;
Idx := (Sender as TListBox).ItemIndex; Idx := (Sender as TListBox).ItemIndex;
@ -510,7 +517,7 @@ var
Escaped: array[0..4095] of Char; Escaped: array[0..4095] of Char;
Txt: string; Txt: string;
begin begin
Tag := (Sender as TControl).Tag; Tag := (Sender as TComponent).Tag;
FormId := Tag shr 16; FormId := Tag shr 16;
CtrlId := Tag and $FFFF; CtrlId := Tag and $FFFF;
Idx := (Sender as TComboBox).ItemIndex; Idx := (Sender as TComboBox).ItemIndex;
@ -531,7 +538,7 @@ var
FormId: Integer; FormId: Integer;
CtrlId: Integer; CtrlId: Integer;
begin begin
Tag := (Sender as TControl).Tag; Tag := (Sender as TComponent).Tag;
FormId := Tag shr 16; FormId := Tag shr 16;
CtrlId := Tag and $FFFF; CtrlId := Tag and $FFFF;
SendEvent(FormId, CtrlId, 'DblClick', ''); SendEvent(FormId, CtrlId, 'DblClick', '');
@ -544,7 +551,7 @@ var
FormId: Integer; FormId: Integer;
CtrlId: Integer; CtrlId: Integer;
begin begin
Tag := (Sender as TControl).Tag; Tag := (Sender as TComponent).Tag;
FormId := Tag shr 16; FormId := Tag shr 16;
CtrlId := Tag and $FFFF; CtrlId := Tag and $FFFF;
SendEvent(FormId, CtrlId, 'Enter', ''); SendEvent(FormId, CtrlId, 'Enter', '');
@ -557,7 +564,7 @@ var
FormId: Integer; FormId: Integer;
CtrlId: Integer; CtrlId: Integer;
begin begin
Tag := (Sender as TControl).Tag; Tag := (Sender as TComponent).Tag;
FormId := Tag shr 16; FormId := Tag shr 16;
CtrlId := Tag and $FFFF; CtrlId := Tag and $FFFF;
SendEvent(FormId, CtrlId, 'Exit', ''); SendEvent(FormId, CtrlId, 'Exit', '');
@ -571,7 +578,7 @@ var
FormId: Integer; FormId: Integer;
CtrlId: Integer; CtrlId: Integer;
begin begin
Tag := (Sender as TControl).Tag; Tag := (Sender as TComponent).Tag;
FormId := Tag shr 16; FormId := Tag shr 16;
CtrlId := Tag and $FFFF; CtrlId := Tag and $FFFF;
SendEvent(FormId, CtrlId, 'KeyDown', IntToStr(Key)); SendEvent(FormId, CtrlId, 'KeyDown', IntToStr(Key));
@ -585,7 +592,7 @@ var
FormId: Integer; FormId: Integer;
CtrlId: Integer; CtrlId: Integer;
begin begin
Tag := (Sender as TControl).Tag; Tag := (Sender as TComponent).Tag;
FormId := Tag shr 16; FormId := Tag shr 16;
CtrlId := Tag and $FFFF; CtrlId := Tag and $FFFF;
SendEvent(FormId, CtrlId, 'KeyUp', IntToStr(Key)); SendEvent(FormId, CtrlId, 'KeyUp', IntToStr(Key));
@ -600,7 +607,7 @@ var
CtrlId: Integer; CtrlId: Integer;
Btn: Integer; Btn: Integer;
begin begin
Tag := (Sender as TControl).Tag; Tag := (Sender as TComponent).Tag;
FormId := Tag shr 16; FormId := Tag shr 16;
CtrlId := Tag and $FFFF; CtrlId := Tag and $FFFF;
Btn := Ord(Button); Btn := Ord(Button);
@ -617,7 +624,7 @@ var
CtrlId: Integer; CtrlId: Integer;
Btn: Integer; Btn: Integer;
begin begin
Tag := (Sender as TControl).Tag; Tag := (Sender as TComponent).Tag;
FormId := Tag shr 16; FormId := Tag shr 16;
CtrlId := Tag and $FFFF; CtrlId := Tag and $FFFF;
Btn := Ord(Button); Btn := Ord(Button);
@ -633,7 +640,7 @@ var
FormId: Integer; FormId: Integer;
CtrlId: Integer; CtrlId: Integer;
begin begin
Tag := (Sender as TControl).Tag; Tag := (Sender as TComponent).Tag;
FormId := Tag shr 16; FormId := Tag shr 16;
CtrlId := Tag and $FFFF; CtrlId := Tag and $FFFF;
SendEvent(FormId, CtrlId, 'MouseMove', SendEvent(FormId, CtrlId, 'MouseMove',
@ -647,7 +654,7 @@ var
FormId: Integer; FormId: Integer;
CtrlId: Integer; CtrlId: Integer;
begin begin
Tag := (Sender as TControl).Tag; Tag := (Sender as TComponent).Tag;
FormId := Tag shr 16; FormId := Tag shr 16;
CtrlId := Tag and $FFFF; CtrlId := Tag and $FFFF;
SendEvent(FormId, CtrlId, 'Click', ''); SendEvent(FormId, CtrlId, 'Click', '');
@ -660,7 +667,7 @@ var
FormId: Integer; FormId: Integer;
CtrlId: Integer; CtrlId: Integer;
begin begin
Tag := (Sender as TControl).Tag; Tag := (Sender as TComponent).Tag;
FormId := Tag shr 16; FormId := Tag shr 16;
CtrlId := Tag and $FFFF; CtrlId := Tag and $FFFF;
SendEvent(FormId, CtrlId, 'Click', ''); SendEvent(FormId, CtrlId, 'Click', '');
@ -673,7 +680,7 @@ var
FormId: Integer; FormId: Integer;
CtrlId: Integer; CtrlId: Integer;
begin begin
Tag := (Sender as TControl).Tag; Tag := (Sender as TComponent).Tag;
FormId := Tag shr 16; FormId := Tag shr 16;
CtrlId := Tag and $FFFF; CtrlId := Tag and $FFFF;
SendEvent(FormId, CtrlId, 'Change', IntToStr((Sender as TScrollBar).Position)); SendEvent(FormId, CtrlId, 'Change', IntToStr((Sender as TScrollBar).Position));
@ -686,13 +693,39 @@ var
FormId: Integer; FormId: Integer;
CtrlId: Integer; CtrlId: Integer;
begin begin
Tag := (Sender as TControl).Tag; Tag := (Sender as TComponent).Tag;
FormId := Tag shr 16; FormId := Tag shr 16;
CtrlId := Tag and $FFFF; CtrlId := Tag and $FFFF;
SendEvent(FormId, CtrlId, 'Notify', ''); SendEvent(FormId, CtrlId, 'Notify', '');
end; end;
procedure TFormClient.HandleMenuItemClick(Sender: TObject);
var
Tag: Longint;
FormId: Integer;
CtrlId: Integer;
begin
Tag := (Sender as TComponent).Tag;
FormId := Tag shr 16;
CtrlId := Tag and $FFFF;
SendEvent(FormId, CtrlId, 'Click', '');
end;
procedure TFormClient.HandleRadioGroupClick(Sender: TObject);
var
Tag: Longint;
FormId: Integer;
CtrlId: Integer;
begin
Tag := (Sender as TComponent).Tag;
FormId := Tag shr 16;
CtrlId := Tag and $FFFF;
SendEvent(FormId, CtrlId, 'Click', IntToStr((Sender as TRadioGroup).ItemIndex));
end;
procedure TFormClient.HandleFormClose(Sender: TObject; procedure TFormClient.HandleFormClose(Sender: TObject;
var Action: TCloseAction); var Action: TCloseAction);
var var
@ -730,6 +763,10 @@ begin
(CR^.Control as TRadioButton).OnClick := HandleRadioButtonClick; (CR^.Control as TRadioButton).OnClick := HandleRadioButtonClick;
ctScrollBar: ctScrollBar:
(CR^.Control as TScrollBar).OnChange := HandleScrollBarChange; (CR^.Control as TScrollBar).OnChange := HandleScrollBarChange;
ctMenuItem:
(CR^.Control as TMenuItem).OnClick := HandleMenuItemClick;
ctRadioGroup:
(CR^.Control as TRadioGroup).OnClick := HandleRadioGroupClick;
end; end;
end; end;
@ -738,6 +775,7 @@ procedure TFormClient.WireOptEvent(CR: PFormCtrlRec; const EventName: string);
begin begin
if EventName = 'DblClick' then if EventName = 'DblClick' then
begin begin
if CR^.Control is TControl then
(CR^.Control as TControl).OnDblClick := HandleDblClick; (CR^.Control as TControl).OnDblClick := HandleDblClick;
CR^.Bound := CR^.Bound + [beDblClick]; CR^.Bound := CR^.Bound + [beDblClick];
end end
@ -767,16 +805,19 @@ begin
end end
else if EventName = 'MouseDown' then else if EventName = 'MouseDown' then
begin begin
if CR^.Control is TControl then
(CR^.Control as TControl).OnMouseDown := HandleMouseDown; (CR^.Control as TControl).OnMouseDown := HandleMouseDown;
CR^.Bound := CR^.Bound + [beMouseDown]; CR^.Bound := CR^.Bound + [beMouseDown];
end end
else if EventName = 'MouseUp' then else if EventName = 'MouseUp' then
begin begin
if CR^.Control is TControl then
(CR^.Control as TControl).OnMouseUp := HandleMouseUp; (CR^.Control as TControl).OnMouseUp := HandleMouseUp;
CR^.Bound := CR^.Bound + [beMouseUp]; CR^.Bound := CR^.Bound + [beMouseUp];
end end
else if EventName = 'MouseMove' then else if EventName = 'MouseMove' then
begin begin
if CR^.Control is TControl then
(CR^.Control as TControl).OnMouseMove := HandleMouseMove; (CR^.Control as TControl).OnMouseMove := HandleMouseMove;
CR^.Bound := CR^.Bound + [beMouseMove]; CR^.Bound := CR^.Bound + [beMouseMove];
end end
@ -803,6 +844,7 @@ procedure TFormClient.UnwireOptEvent(CR: PFormCtrlRec; const EventName: string);
begin begin
if EventName = 'DblClick' then if EventName = 'DblClick' then
begin begin
if CR^.Control is TControl then
(CR^.Control as TControl).OnDblClick := nil; (CR^.Control as TControl).OnDblClick := nil;
CR^.Bound := CR^.Bound - [beDblClick]; CR^.Bound := CR^.Bound - [beDblClick];
end end
@ -832,16 +874,19 @@ begin
end end
else if EventName = 'MouseDown' then else if EventName = 'MouseDown' then
begin begin
if CR^.Control is TControl then
(CR^.Control as TControl).OnMouseDown := nil; (CR^.Control as TControl).OnMouseDown := nil;
CR^.Bound := CR^.Bound - [beMouseDown]; CR^.Bound := CR^.Bound - [beMouseDown];
end end
else if EventName = 'MouseUp' then else if EventName = 'MouseUp' then
begin begin
if CR^.Control is TControl then
(CR^.Control as TControl).OnMouseUp := nil; (CR^.Control as TControl).OnMouseUp := nil;
CR^.Bound := CR^.Bound - [beMouseUp]; CR^.Bound := CR^.Bound - [beMouseUp];
end end
else if EventName = 'MouseMove' then else if EventName = 'MouseMove' then
begin begin
if CR^.Control is TControl then
(CR^.Control as TControl).OnMouseMove := nil; (CR^.Control as TControl).OnMouseMove := nil;
CR^.Bound := CR^.Bound - [beMouseMove]; CR^.Bound := CR^.Bound - [beMouseMove];
end end
@ -866,7 +911,7 @@ end;
{ ----- Property application ----------------------------------------------- } { ----- Property application ----------------------------------------------- }
procedure TFormClient.ApplyProp(CR: PFormCtrlRec; Key, Value: PChar); procedure TFormClient.ApplyProp(FR: PFormRec; CR: PFormCtrlRec; Key, Value: PChar);
var var
S: string; S: string;
Unesc: array[0..4095] of Char; Unesc: array[0..4095] of Char;
@ -874,6 +919,7 @@ var
Lines: TStringList; Lines: TStringList;
P: PChar; P: PChar;
Start: PChar; Start: PChar;
PCR: PFormCtrlRec;
begin begin
S := StrPas(Key); S := StrPas(Key);
@ -887,6 +933,8 @@ begin
ctGroupBox: (CR^.Control as TGroupBox).Caption := StrPas(Unesc); ctGroupBox: (CR^.Control as TGroupBox).Caption := StrPas(Unesc);
ctRadioButton: (CR^.Control as TRadioButton).Caption := StrPas(Unesc); ctRadioButton: (CR^.Control as TRadioButton).Caption := StrPas(Unesc);
ctPanel: (CR^.Control as TPanel).Caption := StrPas(Unesc); ctPanel: (CR^.Control as TPanel).Caption := StrPas(Unesc);
ctMenuItem: (CR^.Control as TMenuItem).Caption := StrPas(Unesc);
ctRadioGroup: (CR^.Control as TRadioGroup).Caption := StrPas(Unesc);
end; end;
end end
else if S = 'Text' then else if S = 'Text' then
@ -975,6 +1023,26 @@ begin
if Start^ <> #0 then if Start^ <> #0 then
(CR^.Control as TComboBox).Items.Add(StrPas(Start)); (CR^.Control as TComboBox).Items.Add(StrPas(Start));
end; end;
ctRadioGroup:
begin
(CR^.Control as TRadioGroup).Items.Clear;
P := Unesc;
Start := P;
while P^ <> #0 do
begin
if P^ = #10 then
begin
P^ := #0;
(CR^.Control as TRadioGroup).Items.Add(StrPas(Start));
Inc(P);
Start := P;
end
else
Inc(P);
end;
if Start^ <> #0 then
(CR^.Control as TRadioGroup).Items.Add(StrPas(Start));
end;
end; end;
end end
else if S = 'Checked' then else if S = 'Checked' then
@ -983,17 +1051,25 @@ begin
if CR^.CtrlType = ctCheckBox then if CR^.CtrlType = ctCheckBox then
(CR^.Control as TCheckBox).Checked := (N <> 0) (CR^.Control as TCheckBox).Checked := (N <> 0)
else if CR^.CtrlType = ctRadioButton then else if CR^.CtrlType = ctRadioButton then
(CR^.Control as TRadioButton).Checked := (N <> 0); (CR^.Control as TRadioButton).Checked := (N <> 0)
else if CR^.CtrlType = ctMenuItem then
(CR^.Control as TMenuItem).Checked := (N <> 0);
end end
else if S = 'Enabled' then else if S = 'Enabled' then
begin begin
N := StrToIntDef(StrPas(Value), 1); N := StrToIntDef(StrPas(Value), 1);
CR^.Control.Enabled := (N <> 0); if CR^.Control is TControl then
(CR^.Control as TControl).Enabled := (N <> 0)
else if CR^.Control is TMenuItem then
(CR^.Control as TMenuItem).Enabled := (N <> 0);
end end
else if S = 'Visible' then else if S = 'Visible' then
begin begin
N := StrToIntDef(StrPas(Value), 1); N := StrToIntDef(StrPas(Value), 1);
CR^.Control.Visible := (N <> 0); if CR^.Control is TControl then
(CR^.Control as TControl).Visible := (N <> 0)
else if CR^.Control is TMenuItem then
(CR^.Control as TMenuItem).Visible := (N <> 0);
end end
else if S = 'MaxLength' then else if S = 'MaxLength' then
begin begin
@ -1027,6 +1103,7 @@ begin
case CR^.CtrlType of case CR^.CtrlType of
ctListBox: (CR^.Control as TListBox).ItemIndex := N; ctListBox: (CR^.Control as TListBox).ItemIndex := N;
ctComboBox: (CR^.Control as TComboBox).ItemIndex := N; ctComboBox: (CR^.Control as TComboBox).ItemIndex := N;
ctRadioGroup: (CR^.Control as TRadioGroup).ItemIndex := N;
end; end;
end end
else if S = 'Stretch' then else if S = 'Stretch' then
@ -1196,11 +1273,45 @@ begin
except except
end; end;
end; end;
end
else if S = 'Parent' then
begin
if CR^.CtrlType = ctMenuItem then
begin
N := StrToIntDef(StrPas(Value), 0);
PCR := FindCtrl(FR, N);
if PCR <> nil then
begin
if PCR^.Control is TMenu then
(PCR^.Control as TMenu).Items.Add(CR^.Control as TMenuItem)
else if PCR^.Control is TMenuItem then
(PCR^.Control as TMenuItem).Add(CR^.Control as TMenuItem);
end;
end;
end
else if S = 'Columns' then
begin
N := StrToIntDef(StrPas(Value), 1);
if CR^.CtrlType = ctRadioGroup then
(CR^.Control as TRadioGroup).Columns := N;
end
else if S = 'ShortCut' then
begin
N := StrToIntDef(StrPas(Value), 0);
if CR^.CtrlType = ctMenuItem then
(CR^.Control as TMenuItem).ShortCut := N;
end
else if S = 'PopupMenu' then
begin
N := StrToIntDef(StrPas(Value), 0);
PCR := FindCtrl(FR, N);
if (PCR <> nil) and (PCR^.Control is TPopupMenu) and (CR^.Control is TControl) then
(CR^.Control as TControl).PopupMenu := PCR^.Control as TPopupMenu;
end; end;
end; end;
procedure TFormClient.ApplyInlineProps(CR: PFormCtrlRec; P: PChar); procedure TFormClient.ApplyInlineProps(FR: PFormRec; CR: PFormCtrlRec; P: PChar);
var var
Token: array[0..4095] of Char; Token: array[0..4095] of Char;
Key: array[0..63] of Char; Key: array[0..63] of Char;
@ -1248,7 +1359,7 @@ begin
StrCopy(Value, Eq); StrCopy(Value, Eq);
end; end;
ApplyProp(CR, Key, Value); ApplyProp(FR, CR, Key, Value);
end; end;
end; end;
@ -1286,6 +1397,14 @@ begin
Result := ctScrollBar Result := ctScrollBar
else if S = 'MediaPlayer' then else if S = 'MediaPlayer' then
Result := ctMediaPlayer Result := ctMediaPlayer
else if S = 'MainMenu' then
Result := ctMainMenu
else if S = 'PopupMenu' then
Result := ctPopupMenu
else if S = 'MenuItem' then
Result := ctMenuItem
else if S = 'RadioGroup' then
Result := ctRadioGroup
else else
Result := ctUnknown; Result := ctUnknown;
end; end;
@ -1378,7 +1497,7 @@ var
FR: PFormRec; FR: PFormRec;
CR: PFormCtrlRec; CR: PFormCtrlRec;
CType: TCtrlTypeE; CType: TCtrlTypeE;
Ctrl: TControl; Comp: TComponent;
begin begin
FormId := ParseInt(P); FormId := ParseInt(P);
CtrlId := ParseInt(P); CtrlId := ParseInt(P);
@ -1398,62 +1517,82 @@ begin
Exit; Exit;
{ Create the control } { Create the control }
Ctrl := nil; Comp := nil;
case CType of case CType of
ctLabel: ctLabel:
begin begin
Ctrl := TLabel.Create(FR^.Form); Comp := TLabel.Create(FR^.Form);
(Ctrl as TLabel).AutoSize := False; (Comp as TLabel).AutoSize := False;
end; end;
ctEdit: ctEdit:
Ctrl := TEdit.Create(FR^.Form); Comp := TEdit.Create(FR^.Form);
ctButton: ctButton:
Ctrl := TButton.Create(FR^.Form); Comp := TButton.Create(FR^.Form);
ctCheckBox: ctCheckBox:
Ctrl := TCheckBox.Create(FR^.Form); Comp := TCheckBox.Create(FR^.Form);
ctListBox: ctListBox:
Ctrl := TListBox.Create(FR^.Form); Comp := TListBox.Create(FR^.Form);
ctComboBox: ctComboBox:
Ctrl := TComboBox.Create(FR^.Form); Comp := TComboBox.Create(FR^.Form);
ctMemo: ctMemo:
Ctrl := TMemo.Create(FR^.Form); Comp := TMemo.Create(FR^.Form);
ctImage: ctImage:
begin begin
Ctrl := TImage.Create(FR^.Form); Comp := TImage.Create(FR^.Form);
(Ctrl as TImage).AutoSize := False; (Comp as TImage).AutoSize := False;
end; end;
ctGroupBox: ctGroupBox:
Ctrl := TGroupBox.Create(FR^.Form); Comp := TGroupBox.Create(FR^.Form);
ctRadioButton: ctRadioButton:
Ctrl := TRadioButton.Create(FR^.Form); Comp := TRadioButton.Create(FR^.Form);
ctPanel: ctPanel:
Ctrl := TPanel.Create(FR^.Form); Comp := TPanel.Create(FR^.Form);
ctScrollBar: ctScrollBar:
Ctrl := TScrollBar.Create(FR^.Form); Comp := TScrollBar.Create(FR^.Form);
ctMediaPlayer: ctMediaPlayer:
Ctrl := TMediaPlayer.Create(FR^.Form); Comp := TMediaPlayer.Create(FR^.Form);
ctMainMenu:
begin
Comp := TMainMenu.Create(FR^.Form);
FR^.Form.Menu := Comp as TMainMenu;
end;
ctPopupMenu:
Comp := TPopupMenu.Create(FR^.Form);
ctMenuItem:
Comp := TMenuItem.Create(FR^.Form);
ctRadioGroup:
Comp := TRadioGroup.Create(FR^.Form);
end; end;
if Ctrl = nil then if Comp = nil then
Exit; Exit;
{ Set parent and geometry } { Set parent and geometry for visual controls }
if Ctrl is TWinControl then if Comp is TWinControl then
(Ctrl as TWinControl).Parent := FR^.Form begin
else (Comp as TWinControl).Parent := FR^.Form;
Ctrl.Parent := FR^.Form; (Comp as TControl).Left := Left;
(Comp as TControl).Top := Top;
(Comp as TControl).Width := Width;
(Comp as TControl).Height := Height;
end
else if Comp is TControl then
begin
(Comp as TControl).Parent := FR^.Form;
(Comp as TControl).Left := Left;
(Comp as TControl).Top := Top;
(Comp as TControl).Width := Width;
(Comp as TControl).Height := Height;
end;
{ else: non-visual (menus, menu items) — no parent or geometry }
Ctrl.Left := Left; Comp.Tag := (Longint(FormId) shl 16) or CtrlId;
Ctrl.Top := Top;
Ctrl.Width := Width;
Ctrl.Height := Height;
Ctrl.Tag := (Longint(FormId) shl 16) or CtrlId;
{ Create control record } { Create control record }
New(CR); New(CR);
CR^.CtrlId := CtrlId; CR^.CtrlId := CtrlId;
CR^.CtrlType := CType; CR^.CtrlType := CType;
CR^.Control := Ctrl; CR^.Control := Comp;
CR^.Bound := []; CR^.Bound := [];
FR^.Ctrls.Add(CR); FR^.Ctrls.Add(CR);
@ -1462,7 +1601,7 @@ begin
WireAutoEvents(CR); WireAutoEvents(CR);
{ Apply inline properties } { Apply inline properties }
ApplyInlineProps(CR, P); ApplyInlineProps(FR, CR, P);
end; end;
@ -1484,7 +1623,7 @@ begin
if CR = nil then if CR = nil then
Exit; Exit;
ApplyInlineProps(CR, P); ApplyInlineProps(FR, CR, P);
end; end;

View file

@ -70,7 +70,7 @@ Event data varies by event type:
| Event | Data | | Event | Data |
|-----------|-------------------------------| |-----------|-------------------------------|
| Click | (none) | | Click | (none), or `<itemIndex>` (RadioGroup) |
| DblClick | (none) | | DblClick | (none) |
| Change | `"new text"` or `<position>` (ScrollBar) | | Change | `"new text"` or `<position>` (ScrollBar) |
| Select | `<index> "selected text"` | | Select | `<index> "selected text"` |
@ -101,28 +101,38 @@ Event data varies by event type:
| Panel | TPanel | (none) | | Panel | TPanel | (none) |
| ScrollBar | TScrollBar | Change | | ScrollBar | TScrollBar | Change |
| MediaPlayer | TMediaPlayer | (none) | | MediaPlayer | TMediaPlayer | (none) |
| MainMenu | TMainMenu | (none) |
| PopupMenu | TPopupMenu | (none) |
| MenuItem | TMenuItem | Click |
| RadioGroup | TRadioGroup | Click |
MainMenu and PopupMenu use `0 0 0 0` geometry (non-visual). MenuItem
uses `0 0 0 0` geometry and requires a `Parent` property to specify its
parent menu or menu item. One MainMenu per form (auto-attached).
PopupMenu is associated with any control via the `PopupMenu` property.
GroupBox and Panel are cosmetic containers (flat parent model — no child GroupBox and Panel are cosmetic containers (flat parent model — no child
containment in the protocol). RadioButtons are all in one group per form. containment in the protocol). RadioButtons are all in one group per form.
Opt-in events (require EVENT.BIND): Click (Image, GroupBox, Panel), Opt-in events (require EVENT.BIND): Click (Image, GroupBox, Panel),
Notify (MediaPlayer), DblClick, KeyDown, KeyUp, Enter, Exit, Notify (MediaPlayer), DblClick, KeyDown, KeyUp, Enter, Exit,
MouseDown, MouseUp, MouseMove. MouseDown, MouseUp, MouseMove. Menu and RadioGroup components do
not support opt-in events.
## Properties ## Properties
| Property | Applies To | Value Format | | Property | Applies To | Value Format |
|-------------|---------------------------------------------|-------------------------------------------| |-------------|---------------------------------------------|-------------------------------------------|
| Caption | Label, Button, CheckBox, GroupBox, RadioButton, Panel | Quoted string | | Caption | Label, Button, CheckBox, GroupBox, RadioButton, Panel, MenuItem, RadioGroup | Quoted string |
| Text | Edit, ComboBox, Memo | Quoted string (`\n` for line breaks) | | Text | Edit, ComboBox, Memo | Quoted string (`\n` for line breaks) |
| Items | ListBox, ComboBox | Quoted string (`\n`-delimited) | | Items | ListBox, ComboBox, RadioGroup | Quoted string (`\n`-delimited) |
| Checked | CheckBox, RadioButton | 0 or 1 | | Checked | CheckBox, RadioButton, MenuItem | 0 or 1 |
| Enabled | All | 0 or 1 | | Enabled | All | 0 or 1 |
| Visible | All | 0 or 1 | | Visible | All | 0 or 1 |
| MaxLength | Edit | Integer | | MaxLength | Edit | Integer |
| ReadOnly | Edit, Memo | 0 or 1 | | ReadOnly | Edit, Memo | 0 or 1 |
| ScrollBars | Memo | 0-3 (ssNone..ssBoth) | | ScrollBars | Memo | 0-3 (ssNone..ssBoth) |
| ItemIndex | ListBox, ComboBox | Integer (-1 = none) | | ItemIndex | ListBox, ComboBox, RadioGroup | Integer (-1 = none) |
| TabOrder | All windowed controls | Integer | | TabOrder | All windowed controls | Integer |
| Stretch | Image | 0 or 1 | | Stretch | Image | 0 or 1 |
| Center | Image | 0 or 1 | | Center | Image | 0 or 1 |
@ -141,6 +151,10 @@ MouseDown, MouseUp, MouseMove.
| DeviceType | MediaPlayer | Quoted string (e.g., `dtWaveAudio`) | | DeviceType | MediaPlayer | Quoted string (e.g., `dtWaveAudio`) |
| AutoOpen | MediaPlayer | 0 or 1 | | AutoOpen | MediaPlayer | 0 or 1 |
| Command | MediaPlayer | Quoted string (pseudo-property, see below)| | Command | MediaPlayer | Quoted string (pseudo-property, see below)|
| Parent | MenuItem | Integer (ctrlId of parent menu/item) |
| Columns | RadioGroup | Integer (number of columns) |
| ShortCut | MenuItem | Integer (Delphi ShortCut value) |
| PopupMenu | Any TControl | Integer (ctrlId of PopupMenu) |
File path properties (Picture, FileName) are resolved relative to the File path properties (Picture, FileName) are resolved relative to the
client's `BasePath` setting. Subdirectories are allowed (e.g., client's `BasePath` setting. Subdirectories are allowed (e.g.,