====== Vega — Formulare aus LFM-Dateien ======
Eine Oberfläche muss nicht im Quelltext stehen. Das **LFM-Format** beschreibt sie als Datei; das Programm daneben enthält nur noch die Handler und drei Zeilen zum Laden. Damit lässt sich das Fenster ändern, ohne den Quelltext anzufassen — und ohne neu zu übersetzen.
Die Formatreferenz steht in [[lyx_-_programmiersprache:vega:lfm|LFM-Formularformat]]; dieser Guide zeigt den Arbeitsablauf.
→ [[lyx_-_programmiersprache:vega:guides|Vega-Guides]] · [[lyx_-_programmiersprache:vega:guides:komponenten|Komponenten]] · [[lyx_-_programmiersprache:vega:lfm|LFM-Referenz]]
----
===== Die Datei =====
lfm 1
object Form1: TForm
Width = 360
Height = 220
Caption = 'Aus einer Datei geladen'
object Kopf: TPanel
Left = 0
Top = 0
Width = 360
Height = 40
Align = alTop
object Titel: TLabel
Left = 12
Top = 12
Width = 320
Height = 18
Caption = 'Dieser Aufbau steht in guide.lfm'
end
end
object Feld: TEdit
Left = 16
Top = 60
Width = 220
Height = 26
Text = 'Text aendern'
OnChange = FeldGeaendert
end
object Stand: TLabel
Left = 16
Top = 100
Width = 320
Height = 18
Caption = 'bereit'
end
object BtnEnde: TButton
Left = 220
Top = 150
Width = 120
Height = 28
Caption = 'Beenden'
OnClick = EndeKlick
end
end
* ''object Name: TKlasse'' … ''end'' — verschachtelt wie der Steuerelementbaum.
* Eigenschaften stehen als ''Name = Wert''; Zeichenketten in einfachen Anführungszeichen.
* ''Align = alTop'' benutzt die Delphi-Schreibweise der Ausrichtung.
* **Handler stehen ohne Anführungszeichen**: ''OnClick = EndeKlick''. Mit Anführungszeichen meldet der Lader ''rechts von einem Ereignis steht ein Name ohne Anfuehrungszeichen'' — nachgemessen mit lyxc 1.1.3I.
----
===== Das Programm =====
unit Main;
import std.io;
import Vega.Types;
import Vega.Events;
import Vega.Canvas;
import Vega.Theme;
import Vega.Control;
import Vega.Font;
import Vega.Strings;
import Vega.Lists;
import Vega.Style;
import Vega.StdCtrls;
import Vega.Edits;
import Vega.Layout;
import Vega.Backend;
import Vega.BackendAuto;
import Vega.Forms;
import Vega.LfmValue;
import Vega.LfmParse;
import Vega.LfmClasses;
import Vega.LfmLoad;
var g_app: TApplication;
var g_form: TForm;
var g_stand: TLabel;
fn FeldGeaendert(sender: TControl): void {
var e: TEdit := sender as TEdit;
var t: pchar := e.GetText();
g_stand.SetCaption(t);
}
fn EndeKlick(sender: TControl): void {
g_app.Terminate();
}
fn main(): int64 {
g_app := new TApplication(NewPlatformBackend());
if (!g_app.Ready) { PrintLn("Kein Fenstersystem erreichbar."); return 1; }
// Ohne Eintragung ist ein Handlername in der Datei nur eine Zeichenkette.
RegisterNotify("FeldGeaendert", FeldGeaendert);
RegisterNotify("EndeKlick", EndeKlick);
var ld: TLfmLoader := new TLfmLoader();
var res: TLfmResult := ld.LoadFile("guide.lfm", null);
if (!res.Ok()) {
PrintLn("Formular mit Fehlern:");
var i: int64 := 0;
while (i < res.ErrorCount()) {
var e: pchar := res.ErrorAt(i);
PrintLn(e);
i := i + 1;
}
}
if (res.Root == null) { PrintLn("Kein Formular geladen."); return 1; }
g_form := res.Root as TForm;
// Controls ueber ihren Namen aus der Datei holen
var sc: TControl := g_form.FindTyped("Stand", "TLabel");
if (sc != null) { g_stand := sc as TLabel; }
if (!g_app.ShowForm(g_form)) { return 2; }
g_app.Run();
g_app.Shutdown();
return 0;
}
Übersetzen: ''lyxc lfm.lyx -I . -I platform/linux -o lfm''. Nachgemessen mit ''lyxc 1.1.3I'', übersetzt fehlerfrei; die Datei wird zur **Laufzeit** relativ zum Arbeitsverzeichnis gelesen.
----
===== Die vier Schritte =====
- **Handler eintragen.** ''RegisterNotify("EndeKlick", EndeKlick)'' verbindet den Namen aus der Datei mit der Funktion. Ohne Eintragung ist der Name nur Text — der Lader meldet das mit Zeilennummer, statt den Knopf später stumm zu lassen. Für die anderen Ereignisarten: ''RegisterMouse'', ''RegisterKey'', ''RegisterChar'', ''RegisterWheel'', ''RegisterPaint''.
- **Laden.** ''ld.LoadFile(pfad, null)'' liefert ein ''TLfmResult''.
- **Fehler ansehen.** ''res.Ok()'', ''res.ErrorCount()'', ''res.ErrorAt(i)''. Vorgabe ist nachsichtig: Das Fenster geht trotz Fehlern auf, damit man **sieht**, wo man steht. ''ld.Strict := true'' bricht stattdessen ab.
- **Controls holen.** ''g_form.FindTyped("Stand", "TLabel")'' sucht über den Namen aus der Datei und prüft dabei die Klasse; ''null'' heißt „nicht gefunden".
----
===== Zurückschreiben =====
''LfmSave(wurzel, pfad)'' schreibt einen bestehenden Steuerelementbaum als LFM-Datei — der Weg, um eine im Code aufgebaute Oberfläche in eine Datei zu überführen und ab da dort zu pflegen.
----
===== Wann sich LFM lohnt =====
* Die Oberfläche ändert sich öfter als die Logik.
* Mehrere Fenster teilen sich Muster, die man in Dateien nebeneinanderlegen kann.
* Jemand ohne Lyx-Kenntnisse soll Beschriftungen oder Positionen anpassen.
Dagegen: Für ein Fenster mit drei Knöpfen ist der Code kürzer als Datei plus Ladecode.
----
===== LFM und @flight_crit =====
**Im Regelzyklus einer sicherheitskritischen Anwendung hat das Laden eines Formulars nichts zu suchen.** Ein ''LoadFile'' liest zur Laufzeit eine Datei, legt dabei Speicher an, baut einen Objektbaum auf und ruft am Ende Handler über Zeiger, deren Ziel erst die Datei bestimmt. Jeder dieser vier Punkte widerspricht dem, was ''@flight_crit'' zusagt: eine Laufzeit, die sich vorab nachweisen lässt.
Der Compiler weist die offensichtlichen Fälle ab (gemessen mit lyxc 1.1.3I):
@flight_crit
fn MitHeap(): int64 { var p: int64 := alloc(64); free(p, 64); return 0; }
sema error (line 8): Speicheranforderung unter @flight_crit nicht erlaubt 'alloc'
sema error: new ist unter @flight_crit nicht erlaubt — der Speicher muss ausserhalb angelegt und hereingereicht werden
sema error: lokales Feld unter @flight_crit nicht erlaubt (es entsteht per mmap, nicht auf dem Stapel)
> **Die Prüfung reicht nur bis zum eigenen Rumpf** ([[https://github.com/SEOLizer/LyX-Compiler/issues/1701|#1701]]). Ruft eine ''@flight_crit''-Funktion einen Helfer, der seinerseits alloziert, meldet der Compiler **nichts** — die Allokation ist dieselbe, sie wird nur nicht mehr gesehen. Ebenso geht ein Aufruf über einen Funktionszeiger durch, obwohl sein Ziel zur Übersetzungszeit nicht feststeht.
>
> Ein ''LfmLoader.LoadFile'' in einer ''@flight_crit''-Funktion würde also **nicht** abgewiesen werden — er wäre trotzdem falsch.
Die brauchbare Aufteilung ist dieselbe wie bei jeder anderen Ressource:
^ Phase ^ Was dort erlaubt ist ^
| **Initialisierung** | Formular laden, Speicher anlegen, Handler eintragen, Fenster öffnen — hier gibt es kein WCET-Budget |
| **Regelzyklus** (''@flight_crit'') | Nur rechnen und auf vorhandene Puffer schreiben; kein ''new'', kein ''alloc'', kein Dateizugriff, keine indirekten Aufrufe |
| **Herunterfahren** | Freigeben, speichern |
Wer eine Oberfläche **über** einer sicherheitskritischen Regelung baut, trennt beide sauber: Die Regelung läuft in ihrem eigenen Rahmen mit vorab angelegten Puffern, die Anzeige liest daraus und wird von der Ereignisschleife bedient. Vega selbst ist **nicht** für DAL-A/B-Code gedacht — es alloziert, zeichnet in Puffer und ruft Handler indirekt; das ist für eine Oberfläche genau richtig und für den Regelzyklus genau falsch.
→ [[lyx_-_programmiersprache:guides:do-178c|DO-178C-Guide]] · [[lyx_-_programmiersprache:sprache:attributes-pragmas|Attribute & Pragmas]]
----
===== Fallstricke =====
* **Handlername nicht eingetragen** — der Lader meldet es; ohne Blick auf ''res'' bleibt das Element stumm.
* **Anführungszeichen um den Handlernamen** — Syntaxfehler mit Zeilennummer (siehe oben).
* **''FindTyped'' liefert ''null''** — Name oder Klasse stimmen nicht; beide stehen in der Datei.
* **Relativer Pfad.** Die Datei wird zur Laufzeit gelesen; das Programm muss aus dem passenden Verzeichnis gestartet werden.
Letzte Aktualisierung: 2026-08-19 — Abschnitt „LFM und @flight_crit" ergänzt (Reichweite der Prüfung mit ''lyxc 1.1.3I'' nachgemessen, [[https://github.com/SEOLizer/LyX-Compiler/issues/1701|#1701]]); Ablauf gegen ''Vega/LfmLoad.lyx'' und ''Vega/LfmClasses.lyx'' erhoben; Datei und Programm mit ''lyxc 1.1.3I'' übersetzt und das Laden nachgemessen.