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.

APPVORLAGE

Was tut die App?

  • "Hinzufügen Text" schreibt den Text aus dem Eingabefeld in die History.

  • "Löschen Eingabefeld" leert das Eingabefeld.

  • Der Titel über dem Eingabefeld ist gekoppelt mit dem Eingabefeld und zeigt den gleichen Inhalt.

  • Die Eingaben werden nicht in einem Model gespeichert.

Schritt 1: Erstellen des Projekts

Falls Sie bei einer Aktion nicht mehr wissen, wie das geht, schauen Sie in den Unterlagen nach…​

  1. Anlegen des Repositories «PROG2_JavaFX_WordCloud» auf GitHub, mit .gitignore für Java

  2. Klonen des Repos in Ihr Working-Directory

  3. 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.wordcloud

    Eventuell werden, je nach verwendeter Gradle-Version, noch andere Werte abgefragt. Oder Sie verwenden gradle init und beantworten dann die Fragen entsprechend.

  4. Version ins lokale Repository commiten und auf GitHub pushen

  5. Öffnen Sie das Projekt in Ihrer bevorzugten IDE

  6. 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.launch weisen wir der durch die Gradle Initialisierung bereits vorhandenen App-Klasse zu, welche die main-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?
Eine separate Main-Klasse ist notwendig, um die Java-FX-Anwendung aus der IDE zu starten. Ansonsten wird ein Runtime-Fehler ausgegeben: "Fehler: Zum Ausführen dieser Anwendung benötigte JavaFX-Runtime-Komponenten fehlen".
Wegen der Java-Modularisierung müssen die JavaFX-Module geladen werden, bevor eine Instanz von Typ javafx.application.Application erstellt werden kann. Dies kann auf 3 Arten erfolgen:

  • Wie in diesem Beispiel: main() in separate Klasse und die Anwendung mit der statischen launch() Methode. Diese initialisiert das JavaFX-Modul, bevor die Instanz der übergebenen Klasse erzeugt wird.

  • Start der Anwendung mit gradle run. Die JavaFX-Module werden vom Gradle javafxplugin vor dem Start geladen.

  • Konfiguration von JavaFX in der IDE und im run command (VM Options) des Projektes wie hier erläutert: https://openjfx.io/openjfx-docs/#IDE-Intellij

  • Nun soll die start-Methode tatsächlich ein Fenster öffnen.
    Dazu erstellen wir als Vorbereitung eine Methode in der Klasse MainWindow, 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.

SBGRUNDEINSTELLUNG

Zum Ausprobieren:

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

    SBGROESSE

  • Platzieren Sie einen HBox Container im Anchor-Pane.

    SBEINFUEGEN

  • 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

    SBANCHOR

    Layout Eigenschaften von HBox

    SBHBOX

    Layout Eigenschaft von Label

    SBLABEL


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.

  • Vergessen Sie nicht, zwischendurch zu speichern (File  Save).

  • Ausserdem können Sie mit Preview  Show Preview in Window das Resultat jederzeit kontrollieren.


Das Layout sieht noch etwas dürftig aus:

Scene Graph

SBSCENEGRAPH

Layout Vorschau

SBVORSCHAU1

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

SBAUSRICHTEN

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

SBVGROW

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:
SBRESULTATLAYOUT

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:

  • Jedes JavaFX-Element, auf das wir im Code zugreifen wollen, muss eine eindeutige id bekommen.

  • Von jeder Methode, die wir implementieren, um Aktionen zu behandeln, muss beim entsprechenden Event der Methodenname hinterlegt werden, d.h. sie wird als Event-Handler-Methode verknüpft.

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 hinzufuegenText und leerenTextEingabe eintragen.

  • 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.

Verknüpfen der Elemente mit dem Controller

26 EinstellungenCode

Verknüpfen der Aktionen mit den Methoden

SBEINSTELLUNGENCODE2

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ü View  Show sample controller skeleton. 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.

MainWindow.fxml
<?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 FXMLLoader muss die FXML Datei finden. Beim Laden muss er dann die Controller Klasse finden (hier MainWindowController), deren Referenz in der FXML Datei steht.

Finden der FXML Datei:

  • Die FXML Datei befindet sich im Ordner src/main/resources in einem Unterverzeichnis mit dem gleichen Package-Level wie die aktuelle Klasse, z.B. ch/zhaw/prog2/wordcloud (Best Practice)

    • Greifen Sie direkt auf die Datei zu mit
      FXMLLoader(getClass().getResource("MainWindow.fxml"))
      wobei ist der angegebene Pfad relativ zum Package-Verzeichnis der Klasse ist.
      Bitte diese Version umsetzen!

  • Die FXML Datei befindet sich im Ordner src/main/resources in einem anderen Unterverzeichnis, z.B. wordcloud

    • Greifen Sie auf die Datei zu mit
      FXMLLoader(getClass().getClassLoader().getResource("wordcloud/MainWindow.fxml"))
      wobei der angegebene Pfad relativ zum src/main/resources Ordner ist.

Finden des Controllers aus der FXML Datei:

  • Geben Sie immer den vollständigen Namen der Klasse an (inklusive Package). Aktuell z.B. ch.zhaw.prog2.wordcloud.MainWindowController.

Ergänzen Sie die openMainWindow(Stage stage) Methode um die 3 Schritte:

  1. Loader für die FXML Datei erstellen

    FXMLLoader loader = new FXMLLoader(... gemäss Hinweis...);
  2. SceneGraph aus der FXML Datei laden, Controller erstellen und FXML-Elemente initialisieren.

    Pane rootNode = loader.load();
  3. 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:

  • Über das Verbinden von Properties mit bind.
    Im aktuellen Fall binden wir das Text-Property von textEingabe an dasjenige von labelTitel:

    labelTitel.textProperty().bind(textEingabe.textProperty());
  • Über das Hinzufügen eines Listeners, der auf die Veränderung des Textes reagiert:

    textEingabe.textProperty().addListener(new ChangeListener<String>() {
        @Override
        public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue)
        {
            labelTitel.setText(newValue);
        }
    });

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:

MainWindow.openMainWindow()
 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:

MainWindowController.java
 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.