Einleitung
Im folgenden Workshop wird eine Applikation erstellt.
Diese Applikation stellt keine Ansprüche an ein in der Praxis taugliches Tool.
Es geht darum, dass Sie eine JavaFX Applikation von Grund auf erstellen und zur Darstellung FXML einsetzen können.
Sie sind angehalten mitzudenken, die Schritte in Ihrer Umgebung auszuführen, auch wenn die Lösung jeweils gezeigt wird.
Ziele
-
Sie erstellen ein neues Gradle Projekt von Grund auf.
-
Sie probieren den JavaFX SceneBuilder aus.
-
Sie erkennen, wie FXML Dateien mit den Klassen zusammenarbeiten.
Voraussetzungen
-
Vorlesung: GUI Foundations, GUI Toolbox
Tooling
-
Installiertes JDK 17+
-
Gradle 7.6+
-
JavaFX SceneBuilder
SceneBuilder
SceneBuilder ist ein Werkzeug zur Bearbeitung von FXML-Dateien und ermöglicht somit die Erstellung und Bearbeitung eines SceneGraphs und die Verknüpfung mit einer zugehörigen Controller-Klasse.
SceneBuilder muss als eigenständiges Werkzeug heruntergeladen und installiert werden.
SceneBuilder kann auch mit den Package-Management-Tools (Windows-Chocolatey, macOS-Homebrew, …) installiert werden.
Beispiel: choco install scenebuilder, brew install scenebuilder
|
| IDE’s können den SceneBuilder einbinden (FXML-Code-Ansicht & SceneBuilder-Ansicht), bzw. liefern bereits eine Version von SceneBuilder mit (IntelliJ). Diese bietet jedoch meist nur einen eingeschränkten Funktionsumfang, weshalb es bei intensiver Arbeit trotzdem Sinn macht, die unabhängige Version zu installieren und aus der IDE aufzurufen (Dateipfad in Einstellungen konfigurieren und mit Kontextmenü Open In SceneBuilder öffnen) |
Vorgehen
Schrittweise wird ein neues, einfaches Projekt angelegt. Dabei wird von einer Vorgabe (was soll die Applikation können, wie soll sie aussehen) ausgegangen.
Zusätzlich falls gewünscht: Das Resultat kann später im Praktikum weiterverwendet, mit einem Model versehen und gemäss MVC-Pattern verbunden werden.
Umsetzung der Applikation
Erstellen einer App, die auf zwei Buttons reagiert, eine Texteingabe erlaubt und einen gebundenen Titel enthält. Die vorgeschlagene Lösung ist eine mögliche unter vielen. Es geht darum, einige Grundlagen zu zeigen.
|
Was tut die App?
|
Schritt 1: Erstellen des Projekts
Falls Sie bei einer Aktion nicht mehr wissen, wie das geht, schauen Sie in den Unterlagen nach…
-
Anlegen des Repositories «PROG2_JavaFX_WordCloud» auf GitHub, mit
.gitignorefür Java -
Klonen des Repos in Ihr Working-Directory
-
Erstellen des Projekts mit Gradle im Terminal / Kommandozeile / GitBash
gradle init --type java-application --dsl groovy --test-framework junit-jupiter \ --project-name PROG2_JavaFX_WordCloud --package ch.zhaw.prog2.wordcloudEventuell werden, je nach verwendeter Gradle-Version, noch andere Werte abgefragt. Oder Sie verwenden
gradle initund beantworten dann die Fragen entsprechend. -
Version ins lokale Repository commiten und auf GitHub pushen
-
Öffnen Sie das Projekt in Ihrer bevorzugten IDE
-
Projekt für JavaFX fit machen durch das Ergänzen von
build.gradle
(siehe auch die Anleitung für Gradle auf der OpenJFX Website)plugins { // ... id 'application' // Adding JavaFX support and dependencies id 'org.openjfx.javafxplugin' version '0.0.13' (1) } // ... Configuration of the java, application and test plugins // Configuration of the JavaFX plugin javafx { (2) version = '17.0.6' (3) modules = [ 'javafx.controls', 'javafx.fxml' ] (4) }1 Hinzufügen des Gradle-Plugins für JavaFX 2 Die Konfiguration des Plugins erfolgt im entsprechenden Block 3 Zum einen können Sie die spezifische JavaFX-Version angeben, die verwendet werden soll. Diese sollte natürlich zur verwendeten Java-Version kompatibel sein. 4 Zum Anderen können sie spezifizieren, welche JavaFX-Module eingebunden werden sollen.
Damit sollte das Projekt bereit sein. Eventuell ist noch ein IDE-Refresh für Gradle notwendig, damit die Anpassungen am build.gradle wirksam werden.
Schritt 2: Vorbereiten der JavaFX Applikation
Beim Start einer JavaFX-Applikation wird jeweils ein «Application» Objekt erstellt. In diesem Objekt läuft der Main-Loop, bis es wieder geschlossen wird. Die Klasse dieses Objekts erbt von javafx.application.Application und muss instanziiert werden über den Aufruf Application.launch, welcher dann implizit die start() Methode aufruft.
|
Aber, eines nach dem anderen:
-
Den Aufruf von
Application.launchweisen wir der durch die Gradle Initialisierung bereits vorhandenen App-Klasse zu, welche diemain-Methode enthält. Dafür müssen wir sie anpassen (siehe unten). -
Als Application Klasse erstellen wir eine neue Klasse, Name =
MainWindow-
nur mal so weit:
public class MainWindow extends Application { @Override public void start(Stage primaryStage) throws Exception { } } -
Und als Anpassung in der Klasse
App(falls nicht vorhanden bitte erstellen)public class App { public static void main(String[] args) { Application.launch(MainWindow.class, args); } }
-
|
Warum eine eigene Klasse App für den Start?
|
-
Nun soll die start-Methode tatsächlich ein Fenster öffnen.
Dazu erstellen wir als Vorbereitung eine Methode in der KlasseMainWindow, welche diesen Vorgang später ausführen wird.-
Neue Methode erstellen, Signatur =
private void openMainWindow(Stage stage)Aktueller Stand:public class MainWindow extends Application { @Override public void start(Stage primaryStage) throws Exception { openMainWindow(primaryStage); } private void openMainWindow(Stage stage) { stage.show(); //leeres Fenster anzeigen } }
-
Schritt 3: SceneBuilder ausprobieren
Damit steht dem ersten Fenster nichts mehr im Wege.
Ausser, dass wir noch keinen Inhalt definiert haben.
Dies wird nun über eine FXML-Datei und SceneBuilder gemacht.
Als Basis braucht SceneBuilder eine FXML-Datei, die Sie am einfachsten aus der IDE heraus erstellen.
Der passende Ort ist das src/main/resources-Verzeichnis, als Name bietet sich MainWindow.fxml an.
Wählen Sie beim Erstellen als "Root Element" ein javafx.scene.layout.AnchorPane.
|
Nachdem die Datei erstellt ist, öffnen Sie diese mit SceneBuilder (über das Kontextmenü der IDE). Sie sehen eine Grundeinstellung. Probieren Sie die verschiedenen Einstellungen und Menüs aus.

Zum Ausprobieren:
-
Vergrössern Sie den Platz für das Anchor-Pane auf 640x480 Pixel.

-
Platzieren Sie einen HBox Container im Anchor-Pane.

-
Platzieren Sie ein Label-Element in den HBox Container.
-
Schalten Sie nun zwischen den einzelnen Elementen um, indem Sie sie im Scene Graph anwählen.
Beachten Sie dazu rechts die Layout-Eigenschaften:Layout Eigenschaften von AnchorPane
Layout Eigenschaften von HBox
Layout Eigenschaft von Label
Schritt 4: Layout erstellen
Erstellen wir nun das abgemachte Layout. Dazu analysieren wir die Vorgabe:
-
Es gibt 4 Zeilen
-
Zuoberst ein Label,
-
dann ein Texteingabefeld,
-
auf der nächsten Zeile zwei Buttons nebeneinander
-
und ab zirka der vertikalen Hälfte ist der untere Teil von einem Textanzeigefeld ausgefüllt.
-
-
Über das Verhalten beim Vergrössern und Verkleinern des Fensters wissen wir nichts.
|
Vorschlag Die vier Zeilen in einer VBox abbilden. Diese VBox enthält zuerst ein Label, dann das Textfeld (TextField). Als Nächstes eine HBox für die beiden Buttons und als letztes Element die TextArea. |
Nach den Übungen von vorhin sollte das kein Problem darstellen. Entfernen Sie die Testelemente aus der AnchorPane und fügen Sie die 6 Elemente ein. Beschriften Sie auch gleich die Buttons entsprechend mit "Hinzufügen Text" und "Löschen Eingabefeld" (Einstellungen in den Properties der Buttons). Es braucht etwas Übung mit der Maus.
|
Das Layout sieht noch etwas dürftig aus:
Scene Graph
|
Layout Vorschau
|
Das Anchor Pane ist zwar wie eingestellt 640x480 Pixel gross, aber die Elemente kleben alle oben links, die Texte auf den Buttons sind nicht lesbar.
Versuchen Sie nun mit den Einstellungen des Layouts das vorgegebene Aussehen zu erreichen.
Experimentieren Sie!
| Es gibt nicht nur einen Satz von möglichen Einstellungen, der zum Ziel führt. Es ist jeweils die Kombination von Einstellungen, wobei Sie immer überlegen müssen, auf welche Elemente sich eine Einstellung auswirkt (z.B. auf das Element selbst oder auf alle Unterelemente). |
Die folgenden Einstellungen führen ebenfalls zum gewünschten Aussehen:
| Einstellung | Bild | Auswirkung |
|---|---|---|
<Alignment> der VBox selbst auf CENTER |
Alle Elemente in der VBox werden horizontal zentriert |
|
VBox innerhalb der AnchorPane |
|
VBox an alle 4 Seiten anheften, damit sie den vollständigen Platz in der AnchorPane ausfüllt. |
<Alignment> der HBox auf CENTER |
Die HBox wurde zentriert in der VBox, nun noch die Elemente in der HBox zentrieren |
|
TextArea <Vgrow> auf ALWAYS |
|
Platz bis zum Ende der VBox füllen |
TextField <Max Width> auf 300 |
Eingabefeldgrösse beschränken |
|
VBox <Top Padding> auf 10 |
Label etwas vom oberen Rand wegnehmen |
|
VBox <Spacing> auf 10 |
Abstand der Elemente in der VBox |
|
HBox <Spacing> auf 10 |
Abstand der Elemente in der HBox (Buttons) |
|
AnchorPane <Bottom Padding> auf 20 |
Unterer Rand (könnte auch weggelassen werden, dann geht die TextArea bis zum unteren Rand). |
Nun sieht das Resultat schon passabel aus:

So, das war der anspruchsvolle Teil. Speichern Sie die FXML Datei, dann fahren wir weiter mit dem Einbinden der FXML Datei in die Anwendung…
Schritt 5: Verbindung herstellen zu unseren Klassen
Die Applikation soll, wie in der Beschreibung am Anfang erwähnt, etwas tun. Dafür wird Code geschrieben, in Klassen, wie gewohnt. Wenn wir mit FXML arbeiten, nimmt uns JavaFX allerdings einen gewichtigen Teil ab: Wir müssen uns nicht um das Event-Handling kümmern!
Trotzdem sind einige Überlegungen (Fragen) notwendig:
-
Auf welche Controls soll im Javacode zugegriffen werden?
-
Welche Controls lösen eine Aktion aus?
-
Wie heisst der Controller, der für dieses Fenster zuständig ist?
|
Es gilt:
|
Antworten:
-
Texte werden im Textfeld eingegeben: Textfeld benennen mit
textEingabe -
Texte werden im Label Titel angezeigt: Label benennen mit
lableTitel -
Texte werden in die TextArea kopiert: TextArea benennen mit
textHistory -
Die Buttons lösen Aktionen aus: Methodennamen
hinzufuegenTextundleerenTextEingabeeintragen. -
Der Klassenname des Controllers ist frei zu vergeben:
ch.zhaw.prog2.wordcloud.MainWindowController
Im Abschnitt Code der Einstellungen finden Sie für jedes Element die Einstellmöglichkeiten.


Damit ist das Layout fertig vorbereitet (bitte speichern) und wir können mit dem Controller beginnen.
| Damit wir nicht alles von Hand machen müssen, bietet SceneBuilder ein Skeleton für den Controller an. Sie finden das im Menü . Probieren Sie es aus und kopieren Sie den Inhalt in die Zwischenablage. |
Schritt 6: Erweitern der Klassen in der IDE
Wechseln Sie wieder in Ihre IDE und erstellen Sie dort eine Klasse mit dem Namen MainWindowController.
Sobald Sie die Klasse offen haben, können Sie die Zwischenablage reinkopieren.
Den SceneBuilder können Sie schliessen, die FXML Datei ist erstellt.
Schauen Sie sich das Resultat an, indem Sie die Datei in Ihrer IDE als Textdatei öffnen:
Sie sehen eine einfache XML Datei mit den Angaben zur Darstellung und den Verbindungsinformationen zu den Klassen im Code.
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<AnchorPane prefHeight="480.0" prefWidth="640.0"
xmlns:fx="http://javafx.com/fxml/1"
xmlns="http://javafx.com/javafx/17"
fx:controller="ch.zhaw.prog2.wordcloud.MainWindowControllerExtended"> (1)
<children>
<VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0" spacing="10.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Label fx:id="labelTitel" text="Label" /> (2)
<TextField fx:id="textEingabe" maxWidth="300.0" /> (2)
<HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0" spacing="10.0">
<children>
<Button mnemonicParsing="false" onAction="#hinzufuegenText" text="Hinzufügen Text" /> (3)
<Button mnemonicParsing="false" onAction="#leerenTextEingabe" text="Löschen Eingabefeld" /> (3)
</children>
</HBox>
<TextArea fx:id="textHistory" editable="false" prefHeight="200.0" prefWidth="200.0" VBox.vgrow="ALWAYS" /> (2)
</children>
<padding>
<Insets top="10.0" />
</padding>
</VBox>
</children>
<padding>
<Insets bottom="20.0" />
</padding>
</AnchorPane>
| 1 | Name der Controller-Klasse, von der eine Instanz beim Laden erstellt und initialisiert wird. |
| 2 | Zuweisung der Elemente zu den mit @FXML annotierten Variablen im Controller (fx:id) |
| 3 | Zuweisung der mit @FXML annotierten Controller-Methoden (onAction), die beim Auslösen des Action-Events des Elements (z.B. Button-Klick) aufgerufen werden sollen. |
Schritt 7: Fenster öffnen
Um diese Darstellung (das Öffnen des Fensters) ausprobieren zu können (ohne jede Funktionalität) bauen wir den Aufruf der FXML Datei sofort ein.
Öffnen Sie dafür die vorbereitete Klasse MainWindow in der IDE.
Die Methode openMainWindow ist noch leer!
Wir benötigen einen Vorgang, welcher die Informationen aus der FXML Datei lädt und die Verbindung zum Controller, den wir bereits vorbereitet haben, herstellt. Dafür bietet JavaFX die Klasse javafx.fxml.FXMLLoader an.
|
Je nachdem, wie Sie die Struktur des Projekts aufgebaut haben, finden sich die Dateien nicht gegenseitig.
Der Finden der FXML Datei:
Finden des Controllers aus der FXML Datei:
|
Ergänzen Sie die openMainWindow(Stage stage) Methode um die 3 Schritte:
-
Loader für die FXML Datei erstellen
FXMLLoader loader = new FXMLLoader(... gemäss Hinweis...); -
SceneGraph aus der FXML Datei laden, Controller erstellen und FXML-Elemente initialisieren.
Pane rootNode = loader.load(); -
Scene initialisieren, setzen und die Stage anzeigen
Scene scene = new Scene(rootNode); stage.setScene(scene); stage.show();
| Falls alles korrekt eingegeben wurde, sollten Sie jetzt die Applikation mit gradle.run ausprobieren können. |
Codieren
Die Umsetzung der Methoden ist nun reine Formsache. Zur Erinnerung, was die Applikation tun soll:
-
«Hinzufügen Text» fügt den Text aus dem Eingabefeld der bestehenden History an.
-
«Löschen Eingabefeld» leert das Eingabefeld.
-
Der Titel ist «gekoppelt» mit dem Eingabefeld und zeigt den gleichen Inhalt.
-
Die Eingaben werden nicht in einem Model gespeichert.
Bindings
Als Erstes verbinden wir den Titel mit dem Eingabefeld. Um diese Verbindung setzten zu können, brauchen wir eine Methode, die nach dem Instanziieren des Controllers (passiert beim loader.load() Befehl) im Controller ausgeführt wird.
Hierzu können wir im Controller eine Methode mit dem Namen initialize definieren. Diese wird, wenn vorhanden, automatisch nach dem Laden der FXML Datei und Verknüpfen mit dem Controller aufgerufen.
|
Es gibt mehrere Möglichkeiten, Verbindungen zwischen Properties herzustellen:
|
Erstellen Sie die public void initialize() Methode.
| Beachten Sie, dass diese Methode im Hintergrund aufgerufen wird und somit nicht mit eigenen Parametern bestückt werden kann. Sie kann also nur Schritte auf bekannten Elementen ausführen (z.B. auf den Controls). |
Falls Sie den Controller mit weiteren Parametern initialisieren möchten (z.B. verknüpfen mit einem Model), können Sie eine entsprechende Methode (z.B. setModel) im Controller definieren und diese Methode in openMainWindow aufrufen, indem Sie den Controller FXMLLoader holen (MainWindowController mainWindowController = loader.getController();) und die Methode aufrufen.
|
Im Code-Beispiel weiter unten ist die Variante mit initialize umgesetzt.
| Sollte auch das geklappt haben, können Sie ausprobieren, ob der Eingabetext jetzt mit dem Titel verbunden ist. |
Die Methode openMainWindow(Stage stage) sollte nun etwa wie folgt aussehen:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void openMainWindow(Stage stage) {
try {
// reads from the same package level as the current class
FXMLLoader loader = new FXMLLoader(getClass().getResource("MainWindow.fxml"));
// load root-node pane from FXML-SceneGraph
Pane rootNode = loader.load();
// Optional, get controller to access specific methods
// MainWindowController mainWindowController = loader.getController(); (1)
// mainWindowController.setModel(...);
// setup scene and prepare the stage
Scene scene = new Scene(rootNode);
stage.setScene(scene);
stage.setMinHeight(200); (2)
stage.setMinWidth(300); (2)
stage.show();
} catch(IOException e) {
System.err.println("Error while loading FXML file: " + e.getMessage());
}
}
| 1 | Optional: Hier kann der vom FXMLLoader erstellte Controller geholt werden, um weitere Konfigurationen vorzunehmen, wie z.B. das Setzen von Attributen oder konfigurieren eines Models. |
| 2 | Optional: Hier können weitere Einstellungen des Fensters (stage) vorgenommen, wie z.B. Minimal-/Maximalgrösse (setMaxWidth,setMaxHeight) festgelegt werden, was oft sinnvoll ist. |
Action Methoden
Es folgt nun die Umsetzung der Methoden hinzufuegenText und leerenTextEingabe, die bereits im MainWindwoController vorbereitet sind.
Die Methode leerenTextEingabe stellt kein Problem dar, die Klasse TextField bietet eine Methode clear() an.
Das Hinzufügen des Textes zu einem bestehenden Inhalt ist ebenfalls schnell geschrieben. Verwenden Sie dazu die Methoden getText() und setText() bzw. appendText() die auf einer TextArea und auf einem TextField vorhanden sind.
Die Klasse MainWindowController ist denkbar einfach, weil wir das EventHandling nicht schreiben müssen:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package ch.zhaw.prog2.wordcloud;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
public class MainWindowController {
(1)
@FXML private TextArea textHistory;
@FXML private TextField textEingabe;
@FXML private Label labelTitel;
public void initialize() { (3)
labelTitel.textProperty().bind(textEingabe.textProperty());
}
@FXML (2)
private void hinzufuegenText(ActionEvent event) {
textHistory.appendText(textEingabe.getText() + System.lineSeparator());
}
@FXML (2)
private void leerenTextEingabe(ActionEvent event) {
textEingabe.clear();
}
}
| 1 | Mit @FXML annotierte Felder werden automatisch mit den entsprechenden JavaFX-Elementen aus der FXML-Description initialisiert (→ fx:id Attribute). |
| 2 | Mit @FXML annotierte Methoden werden automatisch als ActionHandler hinzugefügt (→ onAction Attribute). |
| 3 | Die Methode initialize wird automatisch nach dem Initialisieren der @FXML-Elemente im Controller aufgerufen und eignet sich daher gut um weitere Konfigurationen vorzunehmen wie zum Beispiel Verbindungen zwischen Properties herzustellen. |
| Die Applikation ist damit fertig. Sie macht noch nicht viel und speichert auch die Eingaben nicht in einem Model. Im Praktikum werden wir diese Aufgabe noch einmal aufgreifen und mit einem Model versehen. |





