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.
<WRAP center round tip 90%>
LFM = „Lyx Form„. Nicht zu verwechseln mit LBF, dem LyxOS-Bytecode
(lyxc –emit=lbf). Mit LyxOS hat das Format nichts zu tun.
</WRAP>
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 <Name>: <Klasse> … 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 = <item … end> |
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 =====
RegisterNotify
==== Handler nach Typ getrennt ====
// 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;
, 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:
ld.Strict := true
Ein Formular mit einem Tippfehler geht trotzdem auf, damit man sieht,
wo man steht. Wer das nicht will, setzt 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'
.
==== Controls wiederfinden ====
FindTypedvar ed: TControl := f.FindTyped("EdName", "TEdit"); // oder null
prüft die Klasse über ClassName() — der falsche Typ
liefert null statt eines Casts, der später kracht.
===== Speichern =====
Columns = <item … end>
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:
LfmSave(form, "anmeldung.lfm");
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 =====
<WRAP center round alert 90%>
Sammlungen gehen beim Speichern verloren. object Leer: TButton
end
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.
</WRAP>
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.
* examples/lfmdemo.lyxTabOrder — Vega kennt die Eigenschaft nicht; die Reihenfolge
ergibt sich aus dem Baum. Kommt hinzu, sobald TControl sie hat.
===== Beispiel =====
mit examples/data/demo.lfm: dort steht kein
SetBounds und kein SetParent'' — nur Handler, ihre Eintragung und
drei Zeilen zum Laden.
