JavaFX 8
Willemers Informatik-Ecke

Historisches

Im Jahr 2001 erschien die Version JavaFX 2.0. Sie enthielt eine eigene Skriptsprache und war kein großer Erfolg. Nun wurde FX von grundauf erneuert und läuft unter dem Namen Java FX 8. Das alte JavaFX Script entfällt und Hardware-Rendering wird unterstützt. Seit Java 7 ist JavaFX nun in der Standard-Edition (JSE) integriert. Langfristig soll JavaFX Swing ablösen.

Neu gegenüber Swing ist beispielsweise, dass die Gestaltung von Eingabemasken durch FXML, eine XML-basierte Beschreibungssprache erfolgen kann. Für die Gestaltung von Eingabemasken wird CSS unterstützt.

Eclipse und JavaFX

Für eclipse gibt es das Plugin e(fx)clipse. Damit lassen sich auch spezielle Java-FX-Projekte erzeugen.

Java-Versionen für Java FX

Java 8 bringt bereits eine saubere FX-Umgebung mit. Wird Java 7 verwendet, muss die externe Jar jfxrt.jar eingebunden werden, die sich im Verzeichnis lib der JRE befindet.

Weiterer Link zu dem Thema:

Eine JavaFX-Applikation

Eine JavaFX-Applikation erweitert die Klasse javafx.application.Application. Das Programm wird nicht durch die static-Methode main() gestartet, wie andere Java-Applikationen. Stattdessen überschreibt eine FX-Awendung die Methode start(), der als Parameter eine Stage übergeben wird.

Die Stage repräsentiert das Hauptfenster der Anwendung. Sie stellt einen User-Interface-Container zur Verfügung, welcher eine Scene enthält.

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
 
public class FirstFX extends Application {

    public static void main(String[] args) {
        launch(args); // sorgt indirekt für den Aufruf von start
    }
    
    @Override
    public void start(Stage hauptStage) {
        Button button = new Button(); // Damit etwas da ist
        button.setText("Drück mich!");
        // Bereite die Reaktion auf den Button-Klick vor
        button.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                System.out.println("Hab' Dich auch lieb!");
            }
        });
        // Alles zusammensetzen
        StackPane layoutContainer = new StackPane(); // Layout-Container
        layoutContainer.getChildren().add(button);
        Scene scene = new Scene(layoutContainer, 300, 250); // scene beschreibt den Inhalt
        hauptStage.setScene(scene);
        hauptStage.show(); // fehlt das, sieht man gar nichts
    }
}

Werden die Parameter für Breite und Höhe im Konstruktor von Scene nicht angegeben, wird der zur Verfügung stehende Bereich verwendet.

Ausrichten von Elementen mit Pane

Ein Pane ist ein Brett, auf dem Elemente angeordnet werden können. Die Scene von Haus aus nur ein Element auf. Dieses Element ist typischerweise ein Pane, auf dem weitere Panes für die Anordnung der Elemente sorgen.

HBox und VBox Stapelt die Elemente einzeln neben- bzw. übereinander
FlowPane Füllt Reihe auf und bricht bei Platzmangel um
GridPane Im Raster angeordnet, typischerweise für Dialoge
BorderPane Randelemente mit großem Zentrum: Top, Bottom, Left und Right.
StackPane Elemente werden an derselben Position aufeinander gelegt.
TilePane Ordnet die enthaltenen Elemente in gleichbleibend großem Layout.

Eventhandling

Wenn der Button gedrückt wird, soll irgendetwas passieren. Dazu besitzt so ein Button die Methode setOnAction(). Der Parameter dieser Methode ist ein EventHandler. Ein Objekt der Klasse EventHandler wird auf das Ereignis reagieren und entsprechende Aktionen einleiten.

Der EventHandler kann als eigene Klasse implementiert werden, die sich von EventHandler herleitet und die Methode handle() implementiert. Auf diese Weise wird der Aufbau des Dialogs und die Verarbeitung der Inhalte sauber getrennt.

Bei kleineren Dialogen kann es übersichtlicher sein, alles in einem zu machen und die Klasse, die den Dialog aufbaut auch gleich das Interface EventHandler implementieren zu lassen. Dann wird als EventHandler die eigene Klasse this angegeben. Das folgende Beispiel implementiert den EventHandler für das ActionEvent.

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class FirstFX extends Application implements EventHandler<ActionEvent> {

    public static void main(String[] args) {
        launch(args); // sorgt indirekt für den Aufruf von start
    }

    @Override
    public void handle(ActionEvent event) {
        System.out.println("Hab' Dich auch lieb!");
    }

    @Override
    public void start(Stage hauptStage) {
        // hauptStage.setTitle("Erster Versuch in JavaFX");
        Button button = new Button(); // Damit etwas da ist
        button.setText("Drück mich!");

        // Bereite die Reaktion auf den Button-Klick vor
        button.setOnAction(this);
        // Alles zusammensetzen
        BorderPane layoutContainer = new BorderPane(); // Layout-Container
        layoutContainer.setBottom(button);
        Scene scene = new Scene(layoutContainer); // scene beschreibt den Inhalt
        hauptStage.setScene(scene);
        hauptStage.show(); // fehlt das, sieht man gar nichts
    }
}

Wenn die Ereignisse direkt am Kontrollelement bearbeitet werden sollen, kann man auch die Klasse EventHandler direkt im Parameter der Funktion setOnAction() erzeugen und implementieren.

Die Konstruktion, im Parameter von setOnAction() ein anonymes Objekt zu erzeugen, das eine Handle-Funktion implementiert, kann mithilfe von Lambda, das ja seit Java 8 auch zur Verfügung steht, erheblich verkürzt werden. Statt dieser Zeilen:

button.setOnAction(new EventHandler<ActionEvent>() {
     @Override
     public void handle(ActionEvent event) {
         System.out.println("Hab' Dich auch lieb!");
     }
});
funktioniert folgende Zeile genauso gut:
button.setOnAction(event -> System.out.println("Hab' Dich auch lieb!"));
Es ist auch möglich, eine eigene Klasse für die Events zu schreiben. Sie muss EventHandler implementieren und dazu die Methode handle überschreiben.
public class Ereignis implements EventHandler {
...
    @Override
    public void handle(ActionEvent event) {
    ...
    }
}
Damit die Applikation das Event verarbeiten kann, muss sie die Methode handle() im EventHandler implementieren. Deren Parameter enthält die Informationen rund um das Ereignis.

Wer war's?

Die Methode handle erfährt durch ihren Parameter die Randbedinungen eines Ereignisses. So kann die Methode feststellen, welcher Button geklickt wurde. Solange es nur einen Button gibt, ist diese Information nur mäßig spannend. Wird aber eine zentrale Ereignismethode für mehrere Kontrollelemente verwendet, will man einfach wissen, wer der Täter ist. Diese Frage beantwortet die Methode getSource().
@Override
public void handle(ActionEvent event) {
    if (event.getSource()==okButton) {
        System.out.println("Action!");
    } else if (event.getSource()==cancelButton) {
        System.out.println("War alles nur Spaß!");
    }
}

Kontrollelemente

Die Kontrollelemente ermöglichen den Dialog mit dem Anwender. Durch Eingabefelder, Buttons und andere Elemente kann er seine Wünsche an das Programm stellen.

Label

Ein Label zeigt einen Text an. Es dient der Beschriftung von Dialogen, aber auch, um Ergebnisse anzuzeigen. Der Label an sich ist passiv. Es fängt keine Ereignisse, sondern ist einfach nur schön.

Button

Buttons nehmen einen Klick entgegen und führen fast immer zu Aktionen.

TextField

Ein Eingabefeld hat den Zweck, dass der Anwender auch mal einen Namen, eine Zahl oder eine sonstige Textzeile eingeben kann.

CheckBox und ToggleButton

Diese beiden Abarten des Buttons dienen dazu den logischen Status durch einen Haken (CheckBox) oder durch Tiefe (ToggleButton) darzustellen und zu erfassen.

RadioButton

Ein RadioButton entspricht in seiner Funktion einem ToggleButton. Seine Besonderheit ist, dass er in einer ToggleGroup organisiert ist. Von allen zu einer ToggleGroup organisierten RadioButtons kann nur einer aktiviert sein.

ComboBox

Bei der ComboBox handelt es sich um eine Klappliste, aus deren Elementen der Anwender eines auswählen kann. Zur Füllung der Klappliste wird eine ObservableList eingesetzt.

ListView

Die ListView wird wie ComboBox und ChoiceBox mit einer ObservableList gefüllt. Sie nimmt naturgemäß mehr Raum ein und dient nicht in erster Linie der Auswahl sondern der Darstellung von Daten.

TableView

Der TableView entspricht einer ListView, verfügt aber darüber hinaus auch noch Spaltenüberschriften.

Menüs

Einsatz von CSS in Java FX

Jedem Element kann mit der Methode setStyle() eine Eigenschaft übergeben werden.
button.setStyle("-fx-background-color: yellow;");
Für JavaFX sind eigene Style Sheets definiert. Beispielsweise: Elemente können mit einer Id versehen werden, über die dann im CSS referenziert werden kann.
        textfeld.setId("text");
        rahmen.setId("root");
Im CSS kann nun mit #text auf das Element textfeld zugegriffen werden und mit #root auf das Element rahmen.

Um eine externe CSS zuzugreifen, wird die Scene folgende Methode aufgerufen.

scene.getStylesheets().add(this.getClass().getResource("text.css")
        .toExternalForm());

Java FXML und der SceneBuilder

Die Fensterinhalte können auch in einem grafischen Tool zusammengeklickt werden. Das Ergebnis ist eie FXML-Datei, die vom Hauptprogramm als Panel integriert wird.

Links