GUIs mit JavaFX

Table of Contents

Einführung

JavaFX ist das modernste in der Java SE integrierte GUI-Framework zum Erstellen grafischer Benutzeroberflächen (GUI: Graphical User Interface). JavaFX ist eine reine Java-Bibliothek, die zwar, auch deklarative Definitionen von GUIs über XML ermöglicht, die aber im Kern aus Java-Klassen besteht.

Installation

Seit Java 8 liegt das JavaFX-SDK dem JDK bei. Allerdings kann es sein, dass Sie die JavaFX-Bibliothek explizit dem Klassenpfad hinzufügen müssen. Zu finden ist die entsprechende JAR-Datei unter <JRE_HOME>/lib/ext/jfxrt.jar, wobei <JRE_HOME> für das root-Verzeichnis Ihrer JRE-Installation steht.

Die Versionsnummer von JavaFX ist seit Java/JavaFX 8 an die Version von Java angepasst.

VSCode

Damit man JavaFX in VSCode nutzen kann, müssen wir uns JavaFX zuerst von hier herunterladen. Anschliessend entpacken wir die Archivdatei.

In unserem VSCode Java Projekt müssen wir nun die JavaFX Libs einbinden: Um JavaFX als Abhängigkeiten zum Projekt hinzuzufügen, kann man einfach alle JAR-Dateien aus dem lib-Ordner des heruntergeladenen JavaFX-SDK kopieren, zum Beispiel /Users/your-user/Downloads/javafx-sdk-18.0.2/lib/ in die lib-Ordner Ihres Projekts. Alternativ kann man auch über den Java Projects Explorer hinzufügen. Dazu klickt man auf die Schaltfläche + neben den referenzierten Bibliotheken und wählt die JARs der JavaFX-Bibliothek aus, um sie hinzuzufügen.

Als letzter Schritt müssen wir VSCode beibringen, dass wir das Java Programm mit Parameter an die Java VM übergeben und ausführen wollen. Dazu erstellen wir in unserem Projekt im Ordner .vscode eine Datei namens launch.json mit dem folgenden Inhalt:

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "java",
            "name": "Launch BeispielAnwendung",
            "request": "launch",
            "mainClass": "app.Kap16.BeispielAnwendung",
            "projectName": "labproject_3808de47",
            "vmArgs": "--module-path /Users/Dave/Downloads/javafx-sdk-18.0.1/lib/ --add-modules javafx.controls,javafx.fxml"
        }
}

Entscheidend ist die Angabe der vmArgs. Eine detailliertere Anleitung ist hier zu finden.

Architektur von JavaFX

Am einfachsten wir schauen uns zuerst ein Beispiel an:

package app.Kap16;

import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;

public class BeispielAnwendung extends Application {
    public static void main(String[] args) {
        launch(args);
   }

    @Override
    public void start(Stage stage) throws Exception {
        stage.setTitle("Beispielanwendung");
            Button button = new Button();
            button.setText("Klick mich");
            BorderPane root = new BorderPane();
            root.setCenter(button);
            Scene scene = new Scene(root, 300, 250);
            stage.setScene(scene);
            stage.show();
        }
}

Die Ausgabe bei der Ausführung:

Stage

Der Einstiegspunkt jeder JavaFX-Anwendung ist die javafx.application.Application Klasse. Application repräsentiert die Anwendungsklasse und stellt ein Fenster mit Rahmen, Systemmenü und Standardschaltflächen zur Verfügung. Die einzige Methode, die dabei überschrieben werden muss, ist die Methode start(), die als Parameter ein Objekt vom Typ javafx.stage.Stage übergeben bekommt.

Stage bildet an dieser Stelle das Hauptfenster der Anwendung, das alle weiteren Komponenten enthält. Die Methode start() dient dazu, den Inhalt dieses Fensters zu definieren. Um die Anwendung zu starten, reicht es, in der main-Methode die statische Methode launch() aufzurufen, die von Application bereitgestellt wird. Ausserdem darf man nicht vergessen, die Methode show() an dem Stage-Objekt aufzurufen, sonst wird das entsprechende Fenster gar nicht angezeigt.

Scene

Über einzelne Scenes (Instanzen der Klasse javax.scene.Scene) wird definiert, welche Komponenten in einer Anwendung dargestellt werden. Innerhalb einer Scene werden die einzelnen Komponenten als Knoten (nodes) in einer Baumstruktur verwaltet, dem sogenannten Scene Graph. Nodes können ihrerseits mehrere andere Nodes als Kindelemente enthalten, aber nur ein Elternelement haben. Bei Nodes mit Kindelementen spricht man von Branch Nodes, bei Nodes ohne Kindelemente von Leaf Nodes.

Node Typen

Die Basisklasse für alle Scene Graph Elemente bildet die Klasse javafx.scene.Node. Ihr folgt eine ganze Hierarchie an Unterklassen. Diese lassen sich in fünf Hauptkategorien unterteilen:

  • Parent: alle Nodes, die Kindelemente haben können
  • Canvas: Grafikoperationen, mit der sogenannten Canvas-API kann man zweidimensionale Grafiken zeichnen
  • Shape: Oberklasse verschiedener geometrischer Formen
  • ImageView: Darstellung von Bildern
  • MediaView: Integration von Audio- und Videokomponenten

GUI Komponenten

Beschriftungen

Die Klasse javafx.scene.control wird verwendet um Beschriftungen zu GUI Elementen benötigt werden. Den Text der Beschriftung setzen Sie dabei entweder über den Konstruktor oder über die Methode setText():

Label label = new Label("Beschriftung");

Schaltflächen

Buttons

Buttons bezeichnen einfache Schaltflächen. Repräsentiert werden Buttons durch die Klasse Button. Eine einfache Schaltfläche lässt sich folgendermassen erzeugen (gleiches Ergebnis wie oben):

Button button = new Button("Klick mich");

Radiobuttons

Radiobuttons eignen sich gut für Entweder-oder-Auswahlen. Repräsentiert werden Radiobuttons durch die Klasse RadioButton. Mit Hilfe der Klasse ToggleGroup können (und sollten) die einzelnen Radiobuttons ausserdem in Gruppen zusammengefasst werden. Innerhalb einer solchen Gruppe ist dann sichergestellt, dass jeweils nur ein Radiobutton ausgewählt werden kann. Über setSelected() lässt sich zudem der Radiobutton bestimmen, der (vor)selektiert werden soll. Im folgenden Beispiel wird eine ToggleGroup mit drei Radiobuttons erstellt, von denen der Radiobutton mit der Aufschrift Gelb vorselektiert ist:

            ToggleGroup group = new ToggleGroup();
            RadioButton radioButton1 = new RadioButton("Gelb");
            radioButton1.setToggleGroup(group);
            radioButton1.setSelected(true);
            RadioButton radioButton2 = new RadioButton("Blau");
            radioButton2.setToggleGroup(group);
            RadioButton radioButton3 = new RadioButton("Grün");
            radioButton3.setToggleGroup(group);

Toggle-Buttons

Toggle-Buttons (Klasse ToggleButton) eignen sich gut, wenn Sie eine Schaltfläche benötigen, die zwei Zustände darstellen kann (beispielsweise »An« und »Aus«). Über die Methode setSelected(), die einen booleschen Wert entgegennimmt, können Sie dabei zwischen beiden Zuständen wechseln und über isSelected() den aktuellen Zustand ermitteln. Wie Radiobuttons können auch Toggle-Buttons innerhalb einer Gruppe definiert werden. Im Unterschied zu Radiobuttons, bei denen immer genau einer der Buttons ausgewählt ist, ist es bei Toggle-Buttons also innerhalb einer Gruppe nicht zwingend notwendig, dass überhaupt ein Button ausgewählt ist. Standardmässig ist jedoch der erste Button einer Gruppe vorausgewählt:

            ToggleGroup group2 = new ToggleGroup();
            ToggleButton toggleButtonOne = new ToggleButton("Option1");
            toggleButtonOne.setToggleGroup(group2);
            toggleButtonOne.setSelected(true);
            ToggleButton toggleButtonTwo = new ToggleButton("Option2");
            toggleButtonTwo.setToggleGroup(group2);
            ToggleButton toggleButtonThree = new ToggleButton("Option3");
            toggleButtonThree.setToggleGroup(group2);

Checkboxen 

Checkboxen (Klasse CheckBox) bezeichnen Kästchen, über die bestimmte Auswahlen getroffen werden können. Ob eine Checkbox selektiert ist oder nicht, kann programmatisch über die Methode setSelected() bestimmt werden. Des Weiteren kann eine Checkbox über setIndeterminate() als »definiert« oder als »undefiniert« markiert werden. Beide Methoden erwarten jeweils einen booleschen Wert, in der Kombination ergeben sich so insgesamt drei (sichtbare) verschiedene Zustände pro Checkbox:

            CheckBox checkBox1 = new CheckBox("Option1");
            checkBox1.setSelected(false);
            CheckBox checkBox2 = new CheckBox("Option2");
            checkBox2.setSelected(true);
            CheckBox checkBox3 = new CheckBox("Option3");
            checkBox3.setSelected(true);
            checkBox3.setIndeterminate(true);

Choiceboxen

Bei einer Choicebox handelt es sich um eine Aufklappliste, aus der genau ein Wert ausgewählt werden kann. Choiceboxen werden durch die Klasse ChoiceBox repräsentiert. Zu beachten ist dabei, dass die einzelnen Elemente, die ausgewählt werden können, über die Methode setItems() in Form einer ObservableList hinzugefügt werden müssen. Eine ObservableList unterscheidet sich von normalen Listen, darin, dass Änderungen an einer solchen Liste von der jeweiligen GUI-Komponente wahrgenommen werden und sich direkt auf die grafische Darstellung auswirken. Eine solche ObservableList erzeugen Sie beispielsweise wie in folgendem Listing über die Helferklasse FXCollections:

            ChoiceBox<String> choiceBox = new ChoiceBox<String>();
            ObservableList<String> farben = FXCollections.observableArrayList("Rot", "Blau", "Grün");
            choiceBox.setItems(farben);
            farben.add("Orange");


Zunächst wird hierbei eine ObservableList mit den Werten "Rot", "Blau" und "Grün" angelegt, dann über setItems() der Choicebox hinzugefügt und anschliessend um ein weiteres Element ("Orange") ergänzt. Diese Änderung wirkt sich direkt auf die Choicebox aus: Es stehen anschliessend vier Werte in der Choicebox zur Verfügung.

Eingabefelder

Neben Schaltflächen, über die in der Regel bestimmte Aktionen ausgelöst oder Selektionen getroffen werden, benötigen Sie ausserdem Komponenten, über die die Nutzer Eingaben tätigen können. Dazu zählen unter anderem einzeilige Textfelder, Passwortfelder oder mehrzeilige Textfelder. Textfelder und Passwortfelder sehen auf den ersten Blick gleich aus und verfügen auch über ähnliche Methoden. Das wundert wenig, da PasswordField eine Subklasse von TextField ist. Der einzige sichtbare Unterschied ist, dass jedes Textzeichen, das Sie eingeben, bei einem Passwortfeld durch ein Bullet-Symbol auf dem Bildschirm dargestellt wird:

            TextField text = new TextField();
            text.setPromptText("Benutzername");
            PasswordField password = new PasswordField();
            password.setPromptText("Passwort");

Ausserdem ist es bei einer Instanz von PasswordField im Gegensatz zu reinen TextField-Instanzen nicht möglich, den Text aus dem Feld zu kopieren oder auszuschneiden: Die Methoden copy() und cut() funktionieren hier nicht.

Für mehrzeilige Textfelder kann die Klasse TextArea verwendet werden.

Menüs

Für das Erstellen von Menüs kommen mehrere Klassen zum Einsatz: Das Menü selbst wird mit Hilfe der Klasse Menu dargestellt, die einzelnen Menüeinträge durch die Klasse MenuItem. Hier gibt es beispielsweise die Sonderformen CheckMenuItem und RadioMenuItem, die jeweils über eine Checkbox respektive einen Radiobutton verfügen. Da Menu selbst auch eine Unterklasse von MenuItem ist, lassen sich Menüs zudem auf sehr einfache Weise schachteln.

            MenuBar menuBar = new MenuBar();
            Menu menu = new Menu("Datei");
            MenuItem menuItem = new MenuItem("Neu");
            CheckMenuItem checkMenuItem = new CheckMenuItem("Auswahl");
            menu.getItems().add(menuItem);
            menu.getItems().add(checkMenuItem);
            menuBar.getMenus().add(menu);

Weitere Standardkomponenten

Weitere Komponenten sind hier zu finden.

Geometrische Komponenten

Im Paket javafx.scene.shape findet man eine ganze Reihe geometrischer Formen wie Kreise (Klasse Circle), Pfade (Klasse Path), Rechtecke (Klasse Rectangle), Ellipsen (Klasse Ellipse) und einfachen Text (Klasse Text).

Diagramme

Im JavaFX-SDK sind verschiedene Diagrammformen enthalten (Paket javafx.scene.chart). Diese reichen von einfachen Balkendiagrammen (BarChart) und Tortendiagrammen (PieChart) über Liniendiagramme (LineChart) bis hin zu spezielleren Diagrammformen wie BubbleChart, ScatterChart und AreaChart.