856 lines
30 KiB
Markdown
856 lines
30 KiB
Markdown
# Remote Forms System
|
|
|
|
A remote GUI system for Windows 3.1. Forms are designed visually in
|
|
Delphi 1.0's IDE, converted to a text protocol by a Linux tool, and
|
|
served to a Delphi 1.0 client over serial. The client creates native
|
|
Windows 3.1 controls and sends user events back to the server.
|
|
|
|
## Architecture
|
|
|
|
```
|
|
Linux Serial Windows 3.1
|
|
+-----------------+ +----------------+ +------------------+
|
|
| Delphi IDE | | | | |
|
|
| design → .DFM | | Transport | | TFormClient |
|
|
| | | | (newline- | | creates native |
|
|
| dfm2form | | delimited) | | controls from |
|
|
| | | | | | commands, sends |
|
|
| .form file | | Server -----> |---->| events back |
|
|
| | | | commands | | |
|
|
| FormServerT | | | | |
|
|
| loads .form, | | <----- Client |<----| |
|
|
| sends commands | | events | | |
|
|
+-----------------+ +----------------+ +------------------+
|
|
```
|
|
|
|
## Components
|
|
|
|
| File | Language | Platform | Purpose |
|
|
|---------------|-----------|----------|---------------------------------------|
|
|
| `dfm2form.c` | C | Linux | Converts binary DFM to `.form` text |
|
|
| `formsrv.h` | C | Linux | Server library header |
|
|
| `formsrv.c` | C | Linux | Server library implementation |
|
|
| `formcli.pas` | Pascal | Win 3.1 | Client form engine (Delphi 1.0) |
|
|
| `protocol.md` | — | — | Protocol specification |
|
|
|
|
## Building
|
|
|
|
```
|
|
make # builds bin/dfm2form and obj/formsrv.o
|
|
make clean # removes obj/ and bin/
|
|
```
|
|
|
|
Requires GCC on Linux. The Delphi unit (`formcli.pas`) is compiled as
|
|
part of a Delphi 1.0 project on Windows.
|
|
|
|
## DFM Converter
|
|
|
|
`dfm2form` reads Delphi 1.0 binary DFM files (TPF0 format) and outputs
|
|
`.form` files containing protocol commands. The form ID in the output
|
|
is a placeholder (`0`); the server assigns a dynamic ID when it streams
|
|
the file to the client.
|
|
|
|
```
|
|
dfm2form <input.dfm> [output.form]
|
|
```
|
|
|
|
**Examples:**
|
|
|
|
```
|
|
dfm2form login.dfm # output to stdout
|
|
dfm2form login.dfm login.form # output to file
|
|
```
|
|
|
|
**Output** is a sequence of protocol commands:
|
|
|
|
```
|
|
FORM.CREATE 0 400 300 "Login"
|
|
CTRL.CREATE 0 1 Label 20 20 100 17 Caption="Username:"
|
|
CTRL.CREATE 0 2 Edit 120 18 200 21 Text="" MaxLength=32 TabOrder=0
|
|
CTRL.CREATE 0 3 Label 20 52 100 17 Caption="Password:"
|
|
CTRL.CREATE 0 4 Edit 120 50 200 21 Text="" MaxLength=32 TabOrder=1
|
|
CTRL.CREATE 0 5 Button 245 90 75 25 Caption="OK" TabOrder=2
|
|
CTRL.CREATE 0 6 Button 160 90 75 25 Caption="Cancel" TabOrder=3
|
|
EVENT.BIND 0 5 Enter
|
|
FORM.SHOW 0
|
|
```
|
|
|
|
The placeholder `0` IDs are replaced at runtime by `formServerSendForm`.
|
|
|
|
The converter maps Delphi class names to protocol control types,
|
|
extracts geometry and properties, and emits `EVENT.BIND` for any
|
|
event handler assignments in the DFM that are not auto-wired (see
|
|
Events below). Unknown control classes are skipped with a warning.
|
|
|
|
## Server Library (C)
|
|
|
|
The server streams `.form` files from disk to a remote client through
|
|
a pluggable transport interface, assigning dynamic form IDs. It also
|
|
receives events from the client and dispatches them to a callback.
|
|
|
|
### Transport Interface
|
|
|
|
Implement `FormTransportT` to connect the server to your communication
|
|
channel:
|
|
|
|
```c
|
|
typedef struct {
|
|
int (*readMessage)(char *buf, int32_t maxLen, void *ctx);
|
|
void (*writeMessage)(const char *buf, void *ctx);
|
|
void *ctx;
|
|
} FormTransportT;
|
|
```
|
|
|
|
- `readMessage` — Read one complete message into `buf`. Return the
|
|
number of bytes read, or 0 if no message is available. Must not
|
|
block.
|
|
- `writeMessage` — Send a null-terminated message string. The
|
|
transport adds framing (e.g., CR+LF for serial).
|
|
- `ctx` — Opaque pointer passed through to both callbacks.
|
|
|
|
### API
|
|
|
|
```c
|
|
// Create/destroy
|
|
FormServerT *formServerCreate(FormTransportT *transport);
|
|
void formServerDestroy(FormServerT *server);
|
|
|
|
// Stream a .form file to the client, assigning a dynamic form ID.
|
|
// Returns the assigned form ID, or -1 on error.
|
|
int32_t formServerSendForm(FormServerT *server, const char *path);
|
|
|
|
// Form visibility
|
|
void formServerShowForm(FormServerT *server, int32_t formId);
|
|
void formServerHideForm(FormServerT *server, int32_t formId);
|
|
|
|
// Destroy a form on the client.
|
|
void formServerDestroyForm(FormServerT *server, int32_t formId);
|
|
|
|
// Update a control property
|
|
void formServerSetProp(FormServerT *server, int32_t formId,
|
|
int32_t ctrlId, const char *prop,
|
|
const char *value);
|
|
|
|
// Bind/unbind opt-in events
|
|
void formServerBindEvent(FormServerT *server, int32_t formId,
|
|
int32_t ctrlId, const char *eventName);
|
|
void formServerUnbindEvent(FormServerT *server, int32_t formId,
|
|
int32_t ctrlId, const char *eventName);
|
|
|
|
// Set event callback
|
|
void formServerSetEventCallback(FormServerT *server,
|
|
EventCallbackT cb, void *userData);
|
|
|
|
// Poll for one incoming event. Returns true if an event was processed.
|
|
bool formServerPollEvent(FormServerT *server);
|
|
```
|
|
|
|
### Event Callback
|
|
|
|
```c
|
|
typedef void (*EventCallbackT)(int32_t formId, int32_t ctrlId,
|
|
const char *eventName, const char *data,
|
|
void *userData);
|
|
```
|
|
|
|
- `formId` / `ctrlId` — Identify which control fired the event.
|
|
- `eventName` — `"Click"`, `"Change"`, `"Select"`, etc.
|
|
- `data` — Event-specific data (may be empty). See the Events
|
|
section below for formats.
|
|
|
|
### Server Example
|
|
|
|
```c
|
|
#include "formsrv.h"
|
|
|
|
void onEvent(int32_t formId, int32_t ctrlId, const char *eventName, const char *data, void *userData) {
|
|
printf("Event: form=%d ctrl=%d event=%s data=%s\n",
|
|
formId, ctrlId, eventName, data);
|
|
|
|
FormServerT *server = (FormServerT *)userData;
|
|
|
|
// React to a button click by updating a label
|
|
if (ctrlId == 5 && strcmp(eventName, "Click") == 0) {
|
|
formServerSetProp(server, formId, 1, "Caption", "\"Clicked!\"");
|
|
}
|
|
}
|
|
|
|
int main(void) {
|
|
FormTransportT transport = { myReadFn, myWriteFn, myCtx };
|
|
FormServerT *server = formServerCreate(&transport);
|
|
|
|
formServerSetEventCallback(server, onEvent, server);
|
|
|
|
int32_t formId = formServerSendForm(server, "login.form");
|
|
|
|
// Main loop
|
|
while (running) {
|
|
formServerPollEvent(server);
|
|
}
|
|
|
|
formServerDestroy(server);
|
|
return 0;
|
|
}
|
|
```
|
|
|
|
## Client Engine (Delphi 1.0)
|
|
|
|
The client receives protocol commands, creates native Windows 3.1
|
|
controls, and sends user events back through the transport.
|
|
|
|
### Transport Interface
|
|
|
|
Subclass `TFormTransport` to provide your communication channel:
|
|
|
|
```pascal
|
|
TFormTransport = class(TObject)
|
|
public
|
|
function ReadMessage(Buf: PChar; BufSize: Integer): Integer; virtual; abstract;
|
|
procedure WriteMessage(Buf: PChar; Len: Integer); virtual; abstract;
|
|
end;
|
|
```
|
|
|
|
- `ReadMessage` — Read one complete message into `Buf`. Return the
|
|
number of bytes read, or 0 if no message is available.
|
|
- `WriteMessage` — Send `Len` bytes from `Buf` as a complete message.
|
|
|
|
PChar-based (not short strings) to handle messages up to 4096 bytes.
|
|
|
|
### Serial Transport Example
|
|
|
|
```pascal
|
|
TSerialTransport = class(TFormTransport)
|
|
private
|
|
FComm: TKPComm;
|
|
FRecvBuf: array[0..4095] of Char;
|
|
FRecvLen: Integer;
|
|
public
|
|
function ReadMessage(Buf: PChar; BufSize: Integer): Integer; override;
|
|
procedure WriteMessage(Buf: PChar; Len: Integer); override;
|
|
end;
|
|
|
|
function TSerialTransport.ReadMessage(Buf: PChar; BufSize: Integer): Integer;
|
|
var
|
|
S: string;
|
|
I: Integer;
|
|
Len: Integer;
|
|
begin
|
|
Result := 0;
|
|
{ Accumulate serial data }
|
|
S := FComm.Input;
|
|
if Length(S) > 0 then
|
|
begin
|
|
Move(S[1], FRecvBuf[FRecvLen], Length(S));
|
|
Inc(FRecvLen, Length(S));
|
|
end;
|
|
{ Scan for newline }
|
|
for I := 0 to FRecvLen - 1 do
|
|
begin
|
|
if FRecvBuf[I] = #10 then
|
|
begin
|
|
Len := I;
|
|
if (Len > 0) and (FRecvBuf[Len - 1] = #13) then
|
|
Dec(Len);
|
|
if Len > BufSize - 1 then
|
|
Len := BufSize - 1;
|
|
Move(FRecvBuf, Buf^, Len);
|
|
Buf[Len] := #0;
|
|
{ Shift remainder }
|
|
Move(FRecvBuf[I + 1], FRecvBuf, FRecvLen - I - 1);
|
|
Dec(FRecvLen, I + 1);
|
|
Result := Len;
|
|
Exit;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TSerialTransport.WriteMessage(Buf: PChar; Len: Integer);
|
|
var
|
|
Msg: string;
|
|
begin
|
|
Msg := StrPas(Buf) + #13#10;
|
|
FComm.Output := Msg;
|
|
end;
|
|
```
|
|
|
|
### Using TFormClient
|
|
|
|
```pascal
|
|
var
|
|
Transport: TSerialTransport;
|
|
Client: TFormClient;
|
|
|
|
{ Setup }
|
|
Transport := TSerialTransport.Create;
|
|
Transport.FComm := KPComm1; { your TKPComm instance }
|
|
Client := TFormClient.Create(Transport);
|
|
|
|
{ Main loop (PeekMessage style) }
|
|
while not Terminated do
|
|
begin
|
|
while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do
|
|
begin
|
|
TranslateMessage(Msg);
|
|
DispatchMessage(Msg);
|
|
end;
|
|
Client.ProcessMessages; { pumps incoming commands }
|
|
Yield;
|
|
end;
|
|
|
|
{ Cleanup }
|
|
Client.Free;
|
|
Transport.Free;
|
|
```
|
|
|
|
`ProcessMessages` calls `ReadMessage` in a loop until no more
|
|
messages are available, dispatching each command as it arrives.
|
|
|
|
## Supported Controls
|
|
|
|
| Type | Delphi Class | Description |
|
|
|---------------|--------------|--------------------------------|
|
|
| `Label` | TLabel | Static text label |
|
|
| `Edit` | TEdit | Single-line text input |
|
|
| `Button` | TButton | Push button |
|
|
| `CheckBox` | TCheckBox | Check box with label |
|
|
| `ListBox` | TListBox | Scrollable list of items |
|
|
| `ComboBox` | TComboBox | Drop-down list with text input |
|
|
| `Memo` | TMemo | Multi-line text input |
|
|
| `Image` | TImage | Bitmap image display (BMP only)|
|
|
| `GroupBox` | TGroupBox | Cosmetic grouping frame |
|
|
| `RadioButton` | TRadioButton | Radio button (one group/form) |
|
|
| `Panel` | TPanel | Cosmetic container panel |
|
|
| `ScrollBar` | TScrollBar | Horizontal or vertical scrollbar|
|
|
| `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 |
|
|
| `BitBtn` | TBitBtn | Button with bitmap glyph |
|
|
| `SpeedButton` | TSpeedButton | Flat toolbar-style button |
|
|
| `TabSet` | TTabSet | Tab strip (no pages) |
|
|
| `Notebook` | TNotebook | Multi-page container |
|
|
| `TabbedNotebook` | TTabbedNotebook | Tabbed multi-page container |
|
|
| `MaskEdit` | TMaskEdit | Masked text input |
|
|
| `Outline` | TOutline | Hierarchical tree list |
|
|
| `Bevel` | TBevel | Cosmetic beveled line/box |
|
|
| `Header` | THeader | Column header bar |
|
|
| `ScrollBox` | TScrollBox | Scrollable container |
|
|
| `StringGrid` | TStringGrid | Editable string grid |
|
|
|
|
### Creating Controls
|
|
|
|
Controls are created with `CTRL.CREATE`, specifying position, size,
|
|
and optional inline properties:
|
|
|
|
```
|
|
CTRL.CREATE <formId> <ctrlId> <type> <left> <top> <width> <height> [Key="val" ...]
|
|
```
|
|
|
|
Example:
|
|
|
|
```
|
|
CTRL.CREATE 1 3 Edit 120 50 200 21 Text="hello" MaxLength=32 TabOrder=1
|
|
```
|
|
|
|
Control IDs are positive integers assigned by the server. They must
|
|
be unique within a form.
|
|
|
|
### Updating Controls
|
|
|
|
Properties can be changed at any time after creation with `CTRL.SET`:
|
|
|
|
```
|
|
CTRL.SET <formId> <ctrlId> Key="val" [Key="val" ...]
|
|
```
|
|
|
|
Example:
|
|
|
|
```
|
|
CTRL.SET 1 3 Text="world" Enabled=0
|
|
```
|
|
|
|
## Properties
|
|
|
|
### Common Properties
|
|
|
|
These properties apply to all or most controls:
|
|
|
|
| Property | Format | Description |
|
|
|-----------|-------------------------------|-------------------------------|
|
|
| Enabled | `0` or `1` | Disable or enable the control |
|
|
| Visible | `0` or `1` | Hide or show the control |
|
|
| TabOrder | Integer | Keyboard tab order within the form (all windowed controls except Label and Image) |
|
|
| PopupMenu | Integer (ctrlId of PopupMenu) | Associates a right-click context menu with the control |
|
|
|
|
### Label, Button, GroupBox
|
|
|
|
| Property | Format |
|
|
|----------|---------------|
|
|
| Caption | Quoted string |
|
|
|
|
The display text for the control.
|
|
|
|
Example: `Caption="Submit"`
|
|
|
|
### Edit
|
|
|
|
| Property | Format | Description |
|
|
|-----------|---------------|------------------------------------|
|
|
| Text | Quoted string | Editable text content |
|
|
| MaxLength | Integer | Max characters (0 = no limit) |
|
|
| ReadOnly | `0` or `1` | Prevent user editing when enabled |
|
|
|
|
Example: `Text="Hello world" MaxLength=50`
|
|
|
|
### CheckBox, RadioButton
|
|
|
|
| Property | Format | Description |
|
|
|----------|---------------|----------------------|
|
|
| Caption | Quoted string | Display text |
|
|
| Checked | `0` or `1` | Checked or unchecked |
|
|
|
|
Example: `Caption="Accept terms" Checked=1`
|
|
|
|
### ListBox
|
|
|
|
| Property | Format | Description |
|
|
|-----------|-------------------------------------|---------------|
|
|
| Items | Quoted string (`\n`-delimited) | Item list |
|
|
| ItemIndex | Integer (-1 = none) | Selected item |
|
|
|
|
Replaces the entire item list. The control is cleared before the
|
|
new items are added.
|
|
|
|
Example: `Items="Red\nGreen\nBlue" ItemIndex=0`
|
|
|
|
### ComboBox
|
|
|
|
| Property | Format | Description |
|
|
|-----------|-------------------------------------|-----------------|
|
|
| Text | Quoted string | Editable text |
|
|
| Items | Quoted string (`\n`-delimited) | Drop-down items |
|
|
| ItemIndex | Integer (-1 = none) | Selected item |
|
|
|
|
Example: `Items="Red\nGreen\nBlue" ItemIndex=1`
|
|
|
|
### Memo
|
|
|
|
| Property | Format | Description |
|
|
|------------|--------------------------------------|------------------|
|
|
| Text | Quoted string (`\n` for line breaks) | Text content |
|
|
| ReadOnly | `0` or `1` | Prevent editing |
|
|
| ScrollBars | Integer 0-3 | Scroll bar style |
|
|
|
|
ScrollBars values:
|
|
- `0` — ssNone (no scroll bars)
|
|
- `1` — ssHorizontal
|
|
- `2` — ssVertical
|
|
- `3` — ssBoth
|
|
|
|
Example:
|
|
|
|
```
|
|
CTRL.SET 1 5 Text="Line one\nLine two\nLine three" ScrollBars=2
|
|
```
|
|
|
|
### Image
|
|
|
|
| Property | Format | Description |
|
|
|-------------|--------------------------|--------------------------|
|
|
| Picture | Quoted string (filename) | BMP file to display |
|
|
| Stretch | `0` or `1` | Stretch to fill bounds |
|
|
| Center | `0` or `1` | Center within bounds |
|
|
| Transparent | `0` or `1` | Transparent background |
|
|
|
|
Picture path is resolved relative to the client's `BasePath` setting.
|
|
Subdirectories are allowed. Only BMP files are supported.
|
|
|
|
Example: `Picture="images\logo.bmp" Stretch=1`
|
|
|
|
**Note:** `dfm2form` does not emit Picture from DFM files because
|
|
image data is stored as a binary blob in the DFM. Set Picture at
|
|
runtime via `CTRL.SET` or by manually editing the `.form` file.
|
|
|
|
### Panel
|
|
|
|
| Property | Format | Description |
|
|
|-------------|---------------|--------------|
|
|
| Caption | Quoted string | Display text |
|
|
| BevelOuter | Integer 0-2 | Outer bevel |
|
|
| BevelInner | Integer 0-2 | Inner bevel |
|
|
| BorderStyle | `0` or `1` | Border |
|
|
|
|
Bevel values: `0` (bvNone), `1` (bvLowered), `2` (bvRaised).
|
|
BorderStyle values: `0` (bsNone), `1` (bsSingle).
|
|
|
|
Example: `BevelOuter=2 BevelInner=1 BorderStyle=0`
|
|
|
|
### ScrollBar
|
|
|
|
| Property | Format | Description |
|
|
|-------------|---------|---------------------------------------|
|
|
| Kind | Integer | `0` (sbHorizontal), `1` (sbVertical) |
|
|
| Min | Integer | Minimum value |
|
|
| Max | Integer | Maximum value |
|
|
| Position | Integer | Current position |
|
|
| LargeChange | Integer | Change on track click |
|
|
| SmallChange | Integer | Change on arrow click |
|
|
|
|
Example: `Kind=1 Min=0 Max=100 Position=50 LargeChange=10 SmallChange=1`
|
|
|
|
### MediaPlayer
|
|
|
|
| Property | Format | Description |
|
|
|------------|---------------|-----------------------------------|
|
|
| FileName | Quoted string | Media file path |
|
|
| DeviceType | Quoted string | MCI device type |
|
|
| AutoOpen | `0` or `1` | Open file automatically |
|
|
| Command | Quoted string | Pseudo-property (triggers method) |
|
|
|
|
FileName path is resolved relative to the client's `BasePath` setting.
|
|
|
|
DeviceType values: `dtAutoSelect`, `dtAVIVideo`, `dtCDAudio`, `dtDAT`,
|
|
`dtDigitalVideo`, `dtMMMovie`, `dtOther`, `dtOverlay`, `dtScanner`,
|
|
`dtSequencer`, `dtVCR`, `dtVideodisc`, `dtWaveAudio`.
|
|
|
|
Command triggers a method call instead of setting a value. Valid
|
|
commands: `Open`, `Play`, `Stop`, `Close`, `Pause`, `Resume`,
|
|
`Rewind`, `Next`, `Previous`.
|
|
|
|
Example: `FileName="sounds\intro.wav" DeviceType="dtWaveAudio" AutoOpen=1`
|
|
|
|
### MainMenu, PopupMenu
|
|
|
|
No type-specific properties. One MainMenu per form (auto-attached).
|
|
PopupMenu is associated with any control via the common `PopupMenu`
|
|
property.
|
|
|
|
### MenuItem
|
|
|
|
| Property | Format | Description |
|
|
|----------|---------------|------------------------------|
|
|
| Caption | Quoted string | Menu item text |
|
|
| Parent | Integer | ctrlId of parent menu/item |
|
|
| Checked | `0` or `1` | Check mark |
|
|
| ShortCut | Integer | Delphi ShortCut value |
|
|
|
|
ShortCut uses Delphi's encoding (virtual key + modifier flags).
|
|
|
|
Example: `Caption="&Open" Parent=2 ShortCut=16463`
|
|
|
|
### RadioGroup
|
|
|
|
| Property | Format | Description |
|
|
|-----------|--------------------------------|---------------------|
|
|
| Caption | Quoted string | Group label |
|
|
| Items | Quoted string (`\n`-delimited) | Radio button labels |
|
|
| ItemIndex | Integer (-1 = none) | Selected item |
|
|
| Columns | Integer | Column count |
|
|
|
|
Example: `Caption="Color" Items="Red\nGreen\nBlue" Columns=2 ItemIndex=0`
|
|
|
|
### BitBtn
|
|
|
|
| Property | Format | Description |
|
|
|-----------|---------------|------------------------|
|
|
| Caption | Quoted string | Button text |
|
|
| Kind | Integer 0-10 | Predefined button kind |
|
|
| Layout | Integer 0-3 | Glyph position |
|
|
| NumGlyphs | Integer 1-4 | Glyph images in bitmap |
|
|
|
|
Kind values: `0` (bkCustom), `1` (bkOK), `2` (bkCancel), `3` (bkHelp),
|
|
`4` (bkYes), `5` (bkNo), `6` (bkClose), `7` (bkAbort), `8` (bkRetry),
|
|
`9` (bkIgnore), `10` (bkAll).
|
|
|
|
Layout values: `0` (blGlyphLeft), `1` (blGlyphRight), `2` (blGlyphTop),
|
|
`3` (blGlyphBottom).
|
|
|
|
NumGlyphs: number of glyph images (up, disabled, clicked, down).
|
|
|
|
Example: `Caption="OK" Kind=1 Layout=0 NumGlyphs=2`
|
|
|
|
### SpeedButton
|
|
|
|
| Property | Format | Description |
|
|
|------------|---------------|-----------------------------|
|
|
| Caption | Quoted string | Button text |
|
|
| Layout | Integer 0-3 | Glyph position |
|
|
| NumGlyphs | Integer 1-4 | Glyph images in bitmap |
|
|
| GroupIndex | Integer | Radio group (0 = no group) |
|
|
| Down | `0` or `1` | Pressed state |
|
|
| AllowAllUp | `0` or `1` | Allow all buttons unpressed |
|
|
|
|
Layout values: `0` (blGlyphLeft), `1` (blGlyphRight), `2` (blGlyphTop),
|
|
`3` (blGlyphBottom).
|
|
|
|
Speed buttons with the same non-zero GroupIndex act as a radio group.
|
|
Down is only meaningful when GroupIndex is non-zero.
|
|
|
|
Example: `GroupIndex=1 Down=1 AllowAllUp=0`
|
|
|
|
### TabSet, Notebook, TabbedNotebook
|
|
|
|
| Property | Format | Description |
|
|
|-----------|--------------------------------|-------------|
|
|
| Items | Quoted string (`\n`-delimited) | Tab/page list |
|
|
| ItemIndex | Integer (-1 = none) | Active tab |
|
|
|
|
Example: `Items="General\nAdvanced\nAbout" ItemIndex=0`
|
|
|
|
### MaskEdit
|
|
|
|
| Property | Format | Description |
|
|
|-----------|---------------|-----------------------------------|
|
|
| Text | Quoted string | Editable text content |
|
|
| MaxLength | Integer | Max characters (0 = no limit) |
|
|
| EditMask | Quoted string | Input mask (mask;save;blank char) |
|
|
|
|
Example: `EditMask="(999) 000-0000;1;_"`
|
|
|
|
### Outline
|
|
|
|
| Property | Format | Description |
|
|
|--------------|--------------------------------|---------------|
|
|
| Items | Quoted string (`\n`-delimited) | Tree items |
|
|
| OutlineStyle | Integer 0-6 | Display style |
|
|
|
|
OutlineStyle values:
|
|
- `0` — osText
|
|
- `1` — osPlusMinusText
|
|
- `2` — osPlusMinus
|
|
- `3` — osPictureText
|
|
- `4` — osPicturePlusMinusText
|
|
- `5` — osTreeText
|
|
- `6` — osTreePictureText
|
|
|
|
Example: `OutlineStyle=5`
|
|
|
|
### Bevel
|
|
|
|
| Property | Format | Description |
|
|
|----------|-------------|----------------|
|
|
| Shape | Integer 0-5 | Bevel shape |
|
|
| Style | `0` or `1` | Lowered/raised |
|
|
|
|
Shape values:
|
|
- `0` — bsBox
|
|
- `1` — bsFrame
|
|
- `2` — bsTopLine
|
|
- `3` — bsBottomLine
|
|
- `4` — bsLeftLine
|
|
- `5` — bsRightLine
|
|
|
|
Style values: `0` (bsLowered), `1` (bsRaised).
|
|
|
|
Example: `Shape=2 Style=1`
|
|
|
|
### Header
|
|
|
|
| Property | Format | Description |
|
|
|----------|--------------------------------|-----------------|
|
|
| Items | Quoted string (`\n`-delimited) | Section headers |
|
|
|
|
Example: `Items="Name\nAge\nCity"`
|
|
|
|
### ScrollBox
|
|
|
|
No type-specific properties.
|
|
|
|
### StringGrid
|
|
|
|
| Property | Format | Description |
|
|
|------------------|---------------------------------------------------------|--------------------------|
|
|
| ColCount | Integer (default 5) | Number of columns |
|
|
| RowCount | Integer (default 5) | Number of rows |
|
|
| FixedCols | Integer (default 1) | Non-scrollable left cols |
|
|
| FixedRows | Integer (default 1) | Non-scrollable top rows |
|
|
| DefaultColWidth | Integer (pixels) | Default column width |
|
|
| DefaultRowHeight | Integer (pixels) | Default row height |
|
|
| Options | Integer (bitmask) | Grid options |
|
|
| Cells | Quoted string (tab-delimited cols, `\n`-delimited rows) | Bulk-load all cells |
|
|
| Cell | Quoted string (`col,row,value`) | Set a single cell |
|
|
|
|
Options bitmask of TGridOption values:
|
|
|
|
| Bit | Value | Option | Description |
|
|
|-----|--------|---------------------|---------------------------|
|
|
| 0 | 0x0001 | goFixedVertLine | Vertical lines on fixed |
|
|
| 1 | 0x0002 | goFixedHorzLine | Horizontal lines on fixed |
|
|
| 2 | 0x0004 | goVertLine | Vertical lines on cells |
|
|
| 3 | 0x0008 | goHorzLine | Horizontal lines on cells |
|
|
| 4 | 0x0010 | goRangeSelect | Allow range selection |
|
|
| 5 | 0x0020 | goDrawFocusSelected | Draw focused cell selected|
|
|
| 6 | 0x0040 | goRowSizing | Allow row resizing |
|
|
| 7 | 0x0080 | goColSizing | Allow column resizing |
|
|
| 8 | 0x0100 | goRowMoving | Allow row moving |
|
|
| 9 | 0x0200 | goColMoving | Allow column moving |
|
|
| 10 | 0x0400 | goEditing | Allow in-place editing |
|
|
| 11 | 0x0800 | goTabs | Tab between cells |
|
|
| 12 | 0x1000 | goThumbTracking | Track scrollbar thumb |
|
|
|
|
Cells is a bulk-load property: columns are separated by tab characters,
|
|
rows by newlines. Row 0 is the first row (typically fixed header).
|
|
Cell sets a single cell value: column and row are zero-based indices.
|
|
|
|
Example: `Cells="Name\tAge\nAlice\t30\nBob\t25"`
|
|
|
|
Example: `Cell="1,2,Hello"`
|
|
|
|
### BasePath
|
|
|
|
`BasePath` is a property on `TFormClient` (not a protocol property).
|
|
Set it before loading forms to specify where file-based properties
|
|
(Picture, FileName) resolve relative paths. Example:
|
|
|
|
```pascal
|
|
Client.BasePath := 'C:\MYAPP';
|
|
```
|
|
|
|
A Picture value of `"images\logo.bmp"` then resolves to
|
|
`C:\MYAPP\images\logo.bmp`.
|
|
|
|
## Events
|
|
|
|
### Auto-wired Events
|
|
|
|
These events are automatically connected when a control is created.
|
|
No `EVENT.BIND` command is needed.
|
|
|
|
| Control Type | Event | Data Sent |
|
|
|-------------|---------|-------------------------------|
|
|
| Button | Click | (none) |
|
|
| CheckBox | Click | (none) |
|
|
| RadioButton | Click | (none) |
|
|
| Edit | Change | `"new text"` |
|
|
| Memo | Change | `"new text"` |
|
|
| ScrollBar | Change | `<position>` |
|
|
| ListBox | Select | `<index> "selected text"` |
|
|
| ComboBox | Select | `<index> "selected text"` |
|
|
| ComboBox | Change | `"new text"` |
|
|
| MenuItem | Click | (none) |
|
|
| RadioGroup | Click | `<itemIndex>` |
|
|
| BitBtn | Click | (none) |
|
|
| SpeedButton | Click | (none) |
|
|
| TabSet | Change | `<tabIndex>` |
|
|
| TabbedNotebook | Change | `<pageIndex>` |
|
|
| MaskEdit | Change | `"new text"` |
|
|
| StringGrid | SelectCell | `<col> <row>` |
|
|
|
|
### Opt-in Events
|
|
|
|
These events require an explicit `EVENT.BIND` command before the
|
|
client will send them. Use `EVENT.UNBIND` to disconnect.
|
|
|
|
| Event | Data Sent | Notes |
|
|
|-----------|-----------------------|-----------------------------------|
|
|
| Click | (none) | Image, GroupBox, Panel only |
|
|
| DblClick | (none) | Double-click on any control |
|
|
| Notify | (none) | MediaPlayer playback notification |
|
|
| KeyDown | `<vkCode>` | Windows virtual key code |
|
|
| KeyUp | `<vkCode>` | Windows virtual key code |
|
|
| Enter | (none) | Control received focus |
|
|
| Exit | (none) | Control lost focus |
|
|
| MouseDown | `<x> <y> <button>` | 0=left, 1=right, 2=middle |
|
|
| MouseUp | `<x> <y> <button>` | 0=left, 1=right, 2=middle |
|
|
| MouseMove | `<x> <y> 0` | Coordinates relative to control |
|
|
| SetEditText | `<col> <row> "text"` | StringGrid in-place edit |
|
|
|
|
### Form Close Event
|
|
|
|
When the user clicks the form's close button, the client sends:
|
|
|
|
```
|
|
EVENT <formId> 0 Close
|
|
```
|
|
|
|
The form is **not** automatically destroyed. The server must send
|
|
`FORM.DESTROY` to close it, or `FORM.HIDE` to hide it, or ignore
|
|
the event to keep it open.
|
|
|
|
### Event Message Format
|
|
|
|
All events from client to server follow this format:
|
|
|
|
```
|
|
EVENT <formId> <ctrlId> <eventName> [<data>]
|
|
```
|
|
|
|
### Binding Example
|
|
|
|
```
|
|
EVENT.BIND 1 3 KeyDown (server sends)
|
|
EVENT 1 3 KeyDown 13 (client sends back when Enter pressed)
|
|
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
|
|
|
|
All strings in the protocol are double-quoted. The following escape
|
|
sequences are recognized:
|
|
|
|
| Escape | Character |
|
|
|--------|-----------------|
|
|
| `\"` | Literal `"` |
|
|
| `\\` | Literal `\` |
|
|
| `\n` | Newline (LF) |
|
|
| `\r` | Carriage return |
|
|
| `\t` | Tab |
|
|
|
|
## Transport Layer
|
|
|
|
The protocol is transport-agnostic. Both the C server and Delphi
|
|
client communicate through abstract interfaces that deliver whole
|
|
messages. The current implementation uses newline-delimited serial
|
|
(CR+LF framing), but the transport can be replaced without changing
|
|
any protocol or application code.
|
|
|
|
### Requirements
|
|
|
|
- `ReadMessage` must be non-blocking (return 0 if no data).
|
|
- `WriteMessage` sends one complete message per call.
|
|
- Transport handles framing — the protocol layer never sees
|
|
delimiters.
|
|
|
|
## Limitations
|
|
|
|
- **RadioButton grouping:** All RadioButtons on a form belong to one
|
|
group. Use RadioGroup for multiple independent radio groups per
|
|
form.
|
|
- **Image format:** Only BMP files are supported for the Picture
|
|
property.
|
|
- **dfm2form and Picture:** The converter cannot extract image
|
|
filenames from DFM files (image data is stored as a binary blob).
|
|
Set Picture at runtime via `CTRL.SET` or by manually editing the
|
|
`.form` file.
|
|
- **GroupBox and Panel:** These are cosmetic-only containers. The
|
|
protocol has no child containment — all controls are direct
|
|
children of the form.
|
|
|
|
## Limits
|
|
|
|
| Limit | Value |
|
|
|---------------------------|-------|
|
|
| Max message length | 4096 bytes |
|
|
| Max controls per form | 256 |
|
|
| Form ID range | 1-65535 (stored in high word of Tag) |
|
|
| Control ID range | 1-65535 (stored in low word of Tag) |
|