Skip to main content
Entwickler Themen
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

Architektur und MVVM

NeitzelLib trennt Anwendungsdaten, sichtbare Controls und beobachtbaren Präsentationszustand. Das Model bleibt eine gewöhnliche JavaBean. Die View besteht aus FXML und JavaFX-Nodes. Das ViewModel stellt die Attribute des Models als Properties bereit und schreibt Property-Änderungen über dessen Setter zurück.

Die Rollen

MVVM steht für Model–View–ViewModel. Die Trennung verhindert, dass Controls selbst Geschäftslogik oder Persistenz enthalten müssen. NeitzelLib erzeugt das ViewModel zur Laufzeit; eine Unterklasse kann zusätzliche Präsentations-Properties ergänzen. Alternativ lassen sich typisierte Java-Klassen generieren.

Ein Controller ist weiterhin optionaler Anwendungscode für Ereignisse, Computed-Methoden und Bildschirmverhalten. Er wird nicht automatisch aus dem Model erzeugt. Eine Component kombiniert einen wiederverwendbaren JavaFX-Root, eigenes FXML und eine Model-Property.

flowchart TD
    App[Anwendung: Model und ApplicationContext] --> Loader[ViewLoader]
    FXML[FXML: Controls und Bindings] --> Loader
    Loader --> View[LoadedView: JavaFX-Root]
    Loader --> VM[ViewModel]
    Model[JavaBean-Model] -->|Initiales Lesen / verzögerte Kindauflösung| VM
    VM -->|Property-Änderung ruft Setter| Model
    View <-->|DirectBinding| VM
    Context[ApplicationContext] --> Controller[Controller / Computed]
    Context --> Converter[Converter]
    Context --> Component[Component: Root und Controller]
    Controller --> View
    Converter --> View
    Component --> View
    Registry[Optionale generierte Registry] --> VM

Im Person Desk ist Person das Model, Desk.fxml die View, PersonViewModel ergänzt title, und DeskController behandelt Buttons und berechnet die Begrüßung. AddressForm zeigt eine Adresse, VisitRow eine Tabellenzeile. Die Abhängigkeiten trennen Bibliothekslaufzeit, Anwendung und optionale Werkzeuge.

Ladeablauf

ViewLoader.load(URL, Object, ApplicationContext) liest zunächst den Controller und dessen ViewModel-Auswahl. Danach wird das ViewModel des Models erzeugt oder wiederverwendet. Der FXMLLoader erhält Controller, Components und Converter aus dem Injection-Kontext. JavaFX injiziert @FXML-Felder und ruft initialize auf.

Erst anschließend erhält ein HasViewModel das ViewModel, sofern das gewählte Objekt ViewModel erweitert. Beliebige Ersatzobjekte erhalten diesen Callback nicht. Danach werden BindingLists in Dokumentreihenfolge angewendet und Tables geöffnet. initialize darf deshalb noch kein gesetztes ViewModel voraussetzen. Die eigenen Bindings einer Component warten auf ihr erstes nicht-null Model.

LoadedView.getRoot() liefert den JavaFX-Root. getViewModel() gibt die generische Instanz beziehungsweise deren Unterklasse zurück; bei einem Ersatzobjekt ohne diese Basisklasse wirft es IllegalStateException. property(name) unterstützt auch Ersatzobjekte mit benannten JavaFX-Properties und liefert für unbekannte Namen null. Ein gewöhnlicher FXMLLoader allein richtet diese Bindings und Injection nicht ein.

Beobachtung und Objektidentität

Ein Attribut besitzt Getter und Setter. Beim Aufbau liest das ViewModel den Bean-Wert. Danach schreiben Property-Änderungen den Setter. Direkte Aufrufe des Bean-Setters erzeugen keine rückwärtigen Benachrichtigungen.

Die Laufzeit verwaltet einen globalen, starken, identitätsbasierten Model-Cache. Dasselbe Objekt verwendet über Views und Kontexte hinweg dasselbe ViewModel; gleiche, aber getrennte Objekte nicht. Die frühe Registrierung ermöglicht zyklische Model-Graphen. Es gibt keine öffentliche Freigabe- oder Eviction-API. Anwendungen mit vielen kurzlebigen Models müssen diese Grenze berücksichtigen.

Komplexe Attribute werden verzögert als Kind-ViewModels aufgelöst; null bleibt null. Ein punktierter Binding-Pfad wird beim Anwenden aufgelöst. Ein später ausgetauschtes Zwischenobjekt führt nicht zum erneuten Verbinden des Blatt-Bindings. Auch Components wenden ihre inneren Bindings derzeit nur beim ersten nicht-null Model an.

Listen

Eine vorhandene List kann durch eine ObservableList umschlossen werden; andere Collections werden kopiert. Der Wrapper wird über den Setter zurückgeschrieben, wenn dessen Parametertyp ihn akzeptiert. List funktioniert, konkrete Typen wie ArrayList oder Set können abweichen. Collections vor dem Laden initialisieren und danach über die beobachtete Liste ändern. Eine alte, unbeobachtete Referenz auf deren Daten sendet keine Änderungsereignisse.

Das Ersetzen einer Collection durch den Bean-Setter aktualisiert kein bestehendes ViewModel. Repeat und Table behandeln Hinzufügen, Entfernen und set(index, replacement) als Entfernen/Hinzufügen. Permutationen und reine Extractor-Update-Ereignisse werden nicht nachgeführt.

Fenster, Kontexte und Ressourcen

Der Injection-Kontext hält gescannte Klassen als Singletons, darunter Controller und Converter. Lädt man denselben Controller in mehrere lebende Views, werden seine @FXML-Felder überschrieben. Die erste Component einer Klasse pro Ladevorgang verwendet ebenfalls die Kontextinstanz; weitere Vorkommen sowie Repeat-/Table-Roots entstehen frisch.

Für unabhängige Fenster empfiehlt sich ein eigener Kontext. Der globale Model-Cache wird dadurch nicht getrennt; unabhängige Entwürfe benötigen unterschiedliche Model-Objekte. Verschachtelte Components erfordern bei wiederverwendeten Kontexten besondere Aufmerksamkeit.

Es gibt kein allgemeines LoadedView.close(), kein Unbind-all und keine ViewModel- oder Container-Disposal-API. Listener und Model-Cache können Objekte festhalten. Das Schließen einer Stage ist keine zugesicherte Bereinigung aller Bindings. Popup-Listener nur einmal installieren; Ressourcen und lang laufende beziehungsweise wiederholt geöffnete Fenster gezielt testen.

Laden, Scene-Änderungen, Properties und Nachrichten gehören auf den JavaFX Application Thread. Kontext und Model-Cache sind nicht threadsicher. Langsame Arbeit außerhalb dieses Threads ausführen und Ergebnisse über Platform.runLater übergeben. Computed-Methoden und Listener sollen schnell bleiben.

JPMS und Reflexion

Die Bibliotheksartefakte sind derzeit automatische Module fx und injection. Je nach Imports benötigt die Anwendung requires fx, requires injection, requires jakarta.inject, requires javafx.controls und requires javafx.fxml.

Model-Pakete müssen für den reflektierenden Zugriff von fx geöffnet sein. Controller- und Component-Pakete benötigen Zugriff durch JavaFX, Bibliothek und Container; ein unqualifiziertes opens your.controls; ist ein einfacher Ausgangspunkt zur Diagnose. Generierte Pakete brauchen geeignete exports/opens; eine Registry wird im benannten Modul mit provides angemeldet.

Ein erfolgreicher Classpath-Start belegt noch keine korrekte Modulkonfiguration. Der tatsächliche Module-Path-Start muss geprüft werden. Der Designer trennt sein eigenes Kit/JavaFX vom Zielprozess der Anwendung.