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

// 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 ===== <WRAP center round alert 90%> Sammlungen gehen beim Speichern verloren.
Columns = <item … 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. * TabOrder — Vega kennt die Eigenschaft nicht; die Reihenfolge ergibt sich aus dem Baum. Kommt hinzu, sobald 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.