====== 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.