Ein erstes “Hello World” von JavaFX mit Maven

https://oer-informatik.de/jfx_03_aufbau_des_scenegraphs

tl/dr; (ca. 4 min Lesezeit): Was passiert unter der Oberfläche in JavaFX? Was ist eine Scene, ein Root Node, ein branch node und ein Leaf node? Der Artikel ist Teil eines JavaFX-Tutorials.

Bühne frei: die Komponenten einer JavaFX-GUI

JavaFX gliedert den Aufbau einer GUI in begrifflicher Anlehnung an ein Theater. Wir gehen die Komponenten an Hand folgender Zeichnung durch:

Der Aufbau einer JavaFX-GUI
Der Aufbau einer JavaFX-GUI

Der Ort, an dem die GUI präsentiert wird ist die Stage, die Bühne des Theaters. Gemeint ist damit das Fenster (bei Desktop-Programmen), dass sich öffnet, wenn ich das Programm starte (oder der gesamte Programmbildschirm bei Tablet/Smartphone-Anwendungen).

Auf der Bühne wird eine Szene (Scene) abgespielt. Eine Szene hat eine bestimmte Größe und enthält den eigentlichen Aufbau. Es können bei Bedarf auch mehrere Szenen vorbereitet werden, die nacheinander auf die Bühne geholt werden.

Der Aufbau einer Szene besteht aus mehreren verschachtelten Elementen. Das erste Element, dass alle weiteren enthält, nennt man root node, den Wurzelknoten. Ein Wurzelknoten kann aus weiteren strukturierenden Verzweigungsknoten (branch node) bestehen (z.B. Listen, Tabellen, Stapeln), die ihrerseits weitere Knoten enhalten.

Die eigentlichen Inhalte (z.B. Texte, Bilder, Knöpfe, Grafiken) stellen Blätter dar (leaf node), die selbst keine weiteren Knoten enthalten können.

Neben dem Bild der Bühne wird für den verschachtelten Aufbau einer GUI oft ein zweites Bild bemüht: das eines Baums mit Wurzel/Stamm, Verzweigungen und Blättern. Das obige Beispiel lässt sich als Baum wie folgt darstellen:

Die Baumstruktur des Scenegraph
Die Baumstruktur des Scenegraph

Diese Baumstruktur wird Scenegraph genannt - ein vorwärtsgerichteter Graph mit Knoten (node) und Kanten, bei dem jeder Knoten genau einen Vorgänger hat.

Wie aber wird dieser Aufbau im JavaFX-Quelltext umgesetzt?

Anpassungen der Projektdateien

Das bereits angelegte JavaFX-Projekt verfügt über drei Dateien, die wir uns im folgenden genauer ansehen (und eine Reihe von Dateien, die wir am Ende löschen können, weil wir sie hierfür nicht benötigen):

Exploreransicht der Dateien des Projekts
Exploreransicht der Dateien des Projekts
  • App.java ist die eigentliche Applikation - hier haben wir am meisten Arbeit vor uns

  • module-info.java - legt die Java-Abhängigkeiten fest

  • pom.xml - konfiguriert das Gesamtprojekt

Der Name App.java für die zentrale Datei, aus der heraus unser JavaFX-Programm startet, ist nicht festgeschrieben. Wir könnten den Namen in der pom.xml (unserer Konfigurationsdatei) jederzeit ändern:

Im Paket de.csbme.ifaxx sucht Maven nach einer Klasse de.csbme.ifaxx.App (falls hier nur App steht, kann es sein, dass der Compiler später mit Warnungen um sich wirft).

Maven startet in dieser Klasse eine public static void main(String[] args)-Methode. Für das Grundgerüst benötigen wir also diese statische Methode.

Eine zweite Methode namens start() benötigt JavaFX, um später unser eigentliches Programm aufzunehmen. Das Grundgerüst der App.java sollte also etwa so aussehen:

Bevor wir dieses Grundgerüst mit Leben füllen, sollten wir unsere Abhängigkeiten noch definieren. Um Javaprogramme schlank zu halten, sind in aktuellen Java-Versionen (>8) nur wenige Bibliotheken direkt im Sprachumfang von Java enthalten. Wir müssen daher in der Datei module-info.java zunächst für unsere App festlegen, welche Module benötigt werden (requires: zwei JavaFX Module) und was von Außen genutzt werden darf (exports: unser komplettes Modul):

Jetzt können wir die Platzhalter in der App.java füllen. Die Methode start() ist das Herzstück unseres ersten eigenen JavaFX-Programms. Hier können wir die oben genannten Grundlagen eins jeden JavaFX-Programms erkennen:

Die Stage erhalten wir als Parameter übergeben, von wem genau schauen wir uns später an. Die Stage ist der Ort, an dem alles angezeigt wird - das Startfenster unseres Programms. Wir schleichen uns von der letzten Zeile der Methode (primaryStage.show();) nach oben.

Die Dinge, die wir auf der Bühne anzeigen nennen wir Scene - also Szenen (primaryStage.setScene(scene);). In unserem Beispiel ist die Scene 300x200 Pixel groß und zeigt etwas an, das hier rootNode genannt wurde (new Scene(rootNode, 300, 250);).

Ein Node ist ein Inhaltselement, das weitere Inhaltselemente enthalten kann (z.B. eine Tabelle, ein Gitter, ein Stapel). In unserem Fall enthält der Stapel (StackPane) nur ein Element, nämlich einen Text (new StackPane(textLeaf);). Diesen Text haben wir in der ersten Zeile der Methode erstellt (Text textLeaf = new Text("Hallo");).

Was jetzt noch fehlt, um ein lauffähiges Programm zu erhalten, sind die fehlenden Importe. Wenn es die IDE nicht alleine schafft, die korrekten Klassen zu finden, dann hilft vielleicht dieser Spickzettel zum vergleichen:

Aufräumen im Programmcode…

In dem Beispielprogramm, das VSCode/Maven anlegt, sind schon einige Komponenten mehr enthalten, als wir am Anfang benötigen. Wir trennen uns von den Dateien, die wir nicht mehr benötigen:

  • Im Projekt liegen unter \src\main\resources\de\csbme\ifaxx die Dateien primary.fxml und secondary.fxml - die können gelöscht werden.

  • Ebenso die beiden Klassen PrimaryController.java und SecondaryController.java unter src\main\java\de\csbme\ifaxx

Ein Blick unter die Oberfläche: Wer ruft hier eigentlich wen auf?

Es scheinen bei JavaFX wie von Zauberhand Dinge zu passieren, die schwer nachvollziehbar sind: In der main()-Methode wird eine launch()-Methode aufgerufen, die nirgends implementiert ist. Stattdessen gibt es eine start()-Methode, die wiederum nirgends aufgerufen wird. Wir müssen hier kurz Licht ins Dunkel bringen, in dem wir uns mal ein UML-Klassendiagramm anschauen:

UML-Klassendiagramm der JavaFX-Klassen App, Application, LauncherImpl
UML-Klassendiagramm der JavaFX-Klassen App, Application, LauncherImpl

Das erste Geheimnis ist schnell gelüftet: die mysteriöse Methode launch()-Methode, die in der main()-Methode aufgerufen wird, ist eine statische Methode der Superklasse Application, von der App erbt.

Das zweite Rätsel bleibt aber: start() ist eine Instanzmethode. Aber an welcher Stelle wird eine Instanz von App gebildet? Und wo wird start() aufgerufen?

Dazu werfen wir ein Blick in ein UML-Sequenzdiagramm, das die Abläufe im Hintergrund etwas erklärt:

UML-Sequenzdiagramm der JavaFX-Klassen App, Application, LauncherImpl
UML-Sequenzdiagramm der JavaFX-Klassen App, Application, LauncherImpl
  • Der Benutzer startet Maven, Maven wiederum ruft die main()-Methode der in der pom.xml hinterlegten Klasse (hier: App) auf. Das hatten wir ja bereits entschlüsselt.

  • In dieser Main-Methode wird launch() aufgerufen, eine statische Methode der Superklasse Application. Diese wiederum ruft eine statische Methode einer weiteren Klasse auf (muss man sich nicht merken, der Vollständigkeit halber: sie heißt LauncherImpl):

  • Der Dreh- und Angelpunkt ist die launchApplication()-Methode dieser mysteriösen Klasse (LauncherImpl). Diese Methode erzeugt die Stage, auf der später alles angezeigt wird, instanziiert die Klasse App und ruft schlussendlich die start()-Methode auf, die dann wiederum die Stage selbst anzeigt (show()). Bei Programmende wird hier noch die Methode stop() der App aufgerufen.

Vielleicht war das für jetzt etwas dick aufgetragen. Ich komme jedoch häufig an den Punkt, an dem ich Dinge erst verstehe (und debuggen kann), wenn mir die dahinterliegende Struktur klar geworden ist. Wer noch etwas mehr über die Hintergründe und Realisierungen erfahren will, der kann sich den Quelltext der Superklasse Application sowie der Klasse LauncherImpl unter diesen Links ansehen.

Ausführen des JavaFX-Projekts

Wie immer können wir unser Programm direkt aus der IDE aufrufen, oder, indem wir die pom.xml mit Maven addressieren:

Nächste Schritte

Dieser Artikel ist ein Teil der Artikelserie zu einer Energiemonitors mit JavaFX.

Als nächstes schauen wir uns die zugrunde liegenden Inhaltselemente branch nodes und leaf nodes mal etwas genauer an: Was sind Widgets, wie baue ich sie ein und wie hinterlege ich Aktionen?

Quellen und offene Ressourcen (OER)

Die Ursprungstexte (als Markdown), Grafiken und zugrunde liegende Diagrammquelltexte finden sich in weiterbearbeitbarer Form im gitlab-Repository unter https://gitlab.com/oer-informatik/java-fx/ersteschritte und sind zur Nutzung als Open Education Resource (OER) freigegeben gemäß der Creative Commons Namensnennung 4.0 International Lizenz (CC BY 4.0). Creative Commons Lizenzvertrag

Kommentare gerne per Mastodon, Verbesserungsvorschläge per gitlab issue (siehe oben). Beitrag teilen: