====== LFM — das Formularformat ======
Ein Textformat, das eine Oberfläche vollständig beschreibt: Aufbau,
Eigenschaften und Ereignisbindungen. Dazu ein **Lader**, der daraus zur
Laufzeit einen lebenden Controlbaum macht, und ein **Schreiber** für den
Rückweg.
Vorbild ist Delphis ''.dfm'' — bewusst, weil dessen Aufbau seit dreißig
Jahren trägt und weil jeder, der Delphi kennt, eine LFM-Datei sofort liest.
**LFM = „Lyx Form"**. Nicht zu verwechseln mit **LBF**, dem LyxOS-Bytecode
(''lyxc --emit=lbf''). Mit LyxOS hat das Format nichts zu tun.
===== Stand =====
^ Schritt ^ Stand ^
| ''Anchors'' in ''ApplyLayout'' | fertig |
| ''Vega/LfmValue.lyx'' — Werttypen | fertig |
| ''Vega/LfmParse.lyx'' — Parser | fertig |
| ''Vega/LfmClasses.lyx'' — Registry, 28 Klassen | fertig |
| ''Vega/LfmLoad.lyx'' — Lader | fertig |
| ''Vega/LfmSave.lyx'' — Schreiber | fertig |
| ''lfmdump'', ''lfmcheck'', ''lfmgen'' | offen |
===== Das Format =====
lfm 1
object Form1: TForm
Left = 200
Top = 108
Width = 480
Height = 360
Caption = 'Anmeldung'
Color = #F5F5F5
OnShow = FormShow
object Panel1: TPanel
Align = alTop
Height = 64
object Titel: TLabel
Left = 16
Caption = 'Bitte anmelden'
end
end
object EdName: TEdit
Left = 16
Width = 200
Anchors = [akLeft, akTop, akRight]
Text = 'Andreas'' Datei'
Hint = 'Erste Zeile' + #13 + #10 + 'Zweite Zeile'
OnChange = NamePruefen
end
object CbArt: TComboBox
Items = ('rot', 'gruen', 'blau')
end
end
Ein Block ist ''object : '' … ''end''. **Die Hierarchie ist
die Verschachtelung selbst** — es gibt kein ''Parent =''-Feld.
==== Werte ====
^ Art ^ Schreibweise ^ Beispiel ^
| Ganzzahl | dezimal, ''-'' erlaubt | ''Left = -8'' |
| Wahrheitswert | ''True'' / ''False'' | ''Visible = False'' |
| Zeichenkette | einfache Anführungszeichen | ''Caption = 'Öffnen''' |
| Bezeichner | unquotiert | ''Align = alTop'' |
| Menge | eckige Klammern | ''Anchors = [akLeft, akRight]'' |
| Farbe | ''#RRGGBB'' oder ''#RRGGBBAA'' | ''Color = #F5F5F5'' |
| Zeichenkettenliste | runde Klammern | ''Items = ('rot', 'grün')'' |
| Sammlung | spitze Klammern | ''Columns = - '' |
Ein Anführungszeichen im Text steht **doppelt**. Steuerzeichen hängt man
als ''#13#10'' an, lange Texte verteilt ''+'' über Zeilen — alles wie in
Delphi. Kommentare mit ''//'' bis Zeilenende; die kennt das DFM nicht, aber
eine von Hand gepflegte Datei gewinnt dadurch.
**Farben** weichen bewusst von Delphi ab: Vega hat ''.vtheme'' mit
''#RRGGBB'', und zwei Schreibweisen in einem Projekt sind eine Fehlerquelle
ohne Gegenwert.
===== Laden =====
// Ohne Eintrag ist ein Handlername in der Datei nur eine Zeichenkette.
RegisterNotify("NamePruefen", NamePruefen);
RegisterNotify("FormShow", FormShow);
var ld: TLfmLoader := new TLfmLoader();
var res: TLfmResult := ld.LoadFile("anmeldung.lfm", null);
if (!res.Ok()) {
var i: int64 := 0;
while (i < res.ErrorCount()) {
var e: pchar := res.ErrorAt(i);
PrintLn(e);
i := i + 1;
}
}
var f: TForm := res.Root as TForm;
==== Handler nach Typ getrennt ====
''RegisterNotify'', ''RegisterMouse'', ''RegisterKey'', ''RegisterChar'',
''RegisterWheel'', ''RegisterPaint'' — sechs Funktionen statt eines
Zeigers, den niemand prüft. Ein vertauschter Typ wäre ein Aufruf mit
falschen Argumenten; der stürzt nicht unbedingt ab, sondern **rechnet
falsch**.
Deshalb meldet der Lader:
Zeile 2: Handler 'MausRunter' hat die falsche Signatur fuer OnClick
Zeile 2: Handler 'GibtEsNicht' ist nicht eingetragen
Zeile 3: unbekannte Eigenschaft 'Bevel' an TButton
Zeile 1: unbekannte Klasse 'TGibtEsNicht'
Ein Formular mit einem Tippfehler geht **trotzdem auf**, damit man sieht,
wo man steht. Wer das nicht will, setzt ''ld.Strict := true''.
==== Controls wiederfinden ====
var ed: TControl := f.FindTyped("EdName", "TEdit"); // oder null
''FindTyped'' prüft die Klasse über ''ClassName()'' — der falsche Typ
liefert ''null'' statt eines Casts, der später kracht.
===== Speichern =====
LfmSave(form, "anmeldung.lfm");
**Nur Abweichungen von der Vorgabe.** Ermittelt über ein frisch erzeugtes
Vergleichsobjekt je Klasse — was gleich ist, fällt weg. Das braucht keine
Vorgabetabelle, die veraltet, und ist automatisch richtig: die Vorgabe ist
per Definition das, was der Konstruktor setzt.
Ein unveränderter Knopf ergibt deshalb genau:
object Leer: TButton
end
Die Reihenfolge ist fest — Position und Größe, Ausrichtung, das Eigene,
Ereignisse zuletzt. Nur so ergeben zwei Speichervorgänge dieselbe Datei,
und nur dann ist ein Vergleich im Repository brauchbar.
===== Grenzen =====
**Sammlungen gehen beim Speichern verloren.** ''Columns = - ''
wird gelesen und aufgebaut, aber nicht zurückgeschrieben. Der Rundlauf
bemerkt das **nicht** — beide Richtungen lassen dieselbe Angabe weg, also
sind die Ausgaben trotzdem gleich.
Ebenso: Handler, die die Anwendung direkt zugewiesen hat
(''btn.OnClick := Foo;'') statt über ''RegisterNotify'', stehen in keiner
Tabelle und haben keinen Namen, den man schreiben könnte.
Bewusst nicht im Format:
* **Keine Ausdrücke.** ''Width = Parent.Width - 16'' machte das Format zu
einer Programmiersprache. Dafür gibt es ''Align'' und ''Anchors''.
* **Keine Bedingungen, keine Schleifen.** Die Datei beschreibt einen
Zustand, kein Verfahren.
* **Keine Formularvererbung.** Mächtig — und eine der besten Quellen für
schwer auffindbare Fehler.
* **''TabOrder''** — Vega kennt die Eigenschaft nicht; die Reihenfolge
ergibt sich aus dem Baum. Kommt hinzu, sobald [[lyx_-_programmiersprache:vega:controls:tcontrol|TControl]] sie hat.
===== Beispiel =====
''examples/lfmdemo.lyx'' mit ''examples/data/demo.lfm'': dort steht **kein**
''SetBounds'' und **kein** ''SetParent'' — nur Handler, ihre Eintragung und
drei Zeilen zum Laden.