JavaFX: Erläuterungen der Applikation

JavaFX Serie

Die Applikation der letzten Blog-Einträge möchte ich jetzt hier einmal im Detail erläutern, da es mir in den ersten Teilen erst einmal um die Erstellung der Oberfläche in fxml gegangen ist.

Das ganze Projekt findet sich auf GitHub unter:
https://github.com/kneitzel/blog-javafx-serieshttps://github.com/kneitzel/blog-java-helloworld-fxml im Verzeichnis „02 helloworld-fxml“.

Die Klasse HelloWorld

Die Klasse HelloWorld enthält unsere JavaFX Applikation.

package helloworld;<br></br><br></br>import javafx.application.Application;<br></br>import javafx.fxml.FXMLLoader;<br></br>import javafx.scene.Parent;<br></br>import javafx.scene.Scene;<br></br>import javafx.stage.Stage;<br></br><br></br>import java.io.IOException;<br></br><br></br>public class HelloWorld extends Application {<br></br><br></br>    public static void main(String[] args) {<br></br>        launch(args);<br></br>    }<br></br><br></br>    @Override<br></br>    public void start(Stage primaryStage) throws IOException {<br></br>        Parent root = FXMLLoader.load(getClass().getResource("HelloWorld.fxml"));<br></br>        Scene scene = new Scene(root);<br></br>        primaryStage.setScene(scene);<br></br>        primaryStage.show();<br></br>    }<br></br><br></br>}

Unsere Klasse erbt von Application, welches die Funktionalität einer JavaFX Applikation bereit stellt und u.a. die Parameter, die mitgegeben werden, für uns auswertet.

public static void main(String[] args) {<br></br>    launch(args);<br></br>}

In der Main Methode wird lediglich die in Application bereits vorhandene Methode launch aufgerufen, welche dann u.a. die Parameter auswertet und eine Instanz unserer Klasse erzeugt.

@Override<br></br>public void start(Stage primaryStage) throws IOException {

In unserer Klasse überschreiben wir die start Methode, welche uns als Parameter eine Stage mitgibt. Dies ist der oberste Container unserer JavaFX Struktur und entspricht dem Fenster.

Parent root = FXMLLoader.load(getClass().getResource("HelloWorld.fxml"));

In start lesen wir als erstes unser fxml File ein. Die Datei laden wir über die Methode getResource der Klasse, welche den ClassLoader nutzt um die Resource innerhalb des ClassPath zu finden. Dabei wird relativ zum Verzeichnis des Package gesucht. Da das Package unserer Klasse „helloworld“ ist, wird die Resource in einem Verzeichnis helloworld gesucht.

Der FXMLLoader lädt nicht nur die Datei sondern erzeugt auch eine Instanz des Controllers und initialisiert auch alle durch das @FXML Attribut versehende Elemente.

Scene scene = new Scene(root);

Über den geladen Inhalt erzeugen wir eine Scene. Dies entspricht sozusagen dem Inhalt eines Fensters.

primaryStage.setScene(scene);

Wir setzen die erzeugte Scene zum Inhalt des Hauptfensters.

primaryStage.show();

Und zuletzt machen wir das Fenster sichtbar.

Die Klasse HelloWorldController

Der Controller ist für alle Aktionen in der View verantwortlich und wird von der View angesprochen. In dem Beispiel habe ich auch die Möglichkeit gezeigt, wie der Controller auf die View zugreifen kann, aber diese Abhängigkeit sollten wir nicht nutzen!
Wichtig: Die Instanzen dieser Klasse werden vom FXMLLoader erzeugt. Wir erzeugen keine eigenen Instanzen!

package helloworld;<br></br><br></br>import javafx.event.ActionEvent;<br></br>import javafx.fxml.FXML;<br></br>import javafx.scene.control.Alert;<br></br>import javafx.scene.control.Button;<br></br>import javafx.scene.control.ButtonType;<br></br><br></br>public class HelloWorldController {<br></br><br></br>    @FXML private Button greetMeButton;<br></br><br></br>    public void closeApplicationAction(ActionEvent e) {<br></br>        System.exit(0);<br></br>    }<br></br><br></br>    public void handleGreetMeButton(ActionEvent e) {<br></br>        Alert alert = new Alert(Alert.AlertType.INFORMATION, "Hallo Anwender!", ButtonType.OK);<br></br>        alert.showAndWait();<br></br>    }<br></br><br></br>    @FXML<br></br>    public void initialize() {<br></br>        greetMeButton.setOnAction(this::handleGreetMeButton);<br></br>    }<br></br>}

Die Klasse unseres Controllers muss von keiner anderen Klasse abgeleitet werden.

@FXML private Button greetMeButton;

In dem Controller können wir uns die Elemente, welche im fxml definiert wurden, vom FXMLLoader geben lassen. Damit der FXMLLoader dies macht, muss die Annotation @FXML gesetzt sein.
Desweiteren muss der Typ stimmen (hier Button) und der Name der Variablen muss mit der fx:id überein stimmen (hier fx:id=“greetMeButton“).

Wichtig: Dies erzeugt eine Abhängigkeit vom Controller zu der View. Diese Abhängigkeit sollte vermieden werden so dass nur eine Abhängigkeit von der View zum Controller (und später auch zum Model) existiert. Dies findet sich nur in diesem Beispiel, da dies etwas ist, das sehr viele Entwickler so machen.

public void closeApplicationAction(ActionEvent e) {
public void handleGreetMeButton(ActionEvent e) {

Wir definieren Methoden, die ein ActionEvent als Parameter nehmen. Diese Methoden können Actions behandeln und werden in der fxml Datei referenziert.

Natürlich können wir diese Methoden auch aus unserem Code heraus nutzen.

@FXML<br></br>public void initialize() {

Es gibt für einen Controller gewisse Events, die wir nutzen können. Ein Event ist das initialize Event. Das @FXML Annotation sorgt dafür, dass der FXMLLoader diese Methode auch ansteuert.

Dies ist wichtig, denn die Variablen, die vom FXMLLoader gesetzt werden (mit @FXML Annotation) werden erst nach dem Konstruktor gesetzt. Somit stehen diese noch nicht im Konstruktor zur Verfügung. Initialisierungsarbeiten, die auch die Controls benötigen, müssen somit innerhalb der initialize() Methode gemacht werden.

greetMeButton.setOnAction(this::handleGreetMeButton);

Hier setzen wir im Code, was für eine Action bei dem Button ausgeführt werden soll. Dies hätte auch über das fxml gesetzt werden können (so wie beim anderen Button).

Die HelloWorld.fxml Ressource

In der fxml Datei haben wir unsere Oberfläche beschrieben.

<?xml version="1.0" encoding="UTF-8"?><br></br><br></br><?import javafx.scene.control.Button?><br></br><?import javafx.scene.control.Label?><br></br><?import javafx.scene.layout.Pane?><br></br><?import javafx.scene.text.Font?><br></br><br></br><Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="145.0" prefWidth="237.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="helloworld.HelloWorldController"><br></br>   <children><br></br>      <Label alignment="CENTER" contentDisplay="CENTER" layoutX="-1.0" layoutY="14.0" prefHeight="53.0" prefWidth="237.0" text="Hallo Welt" textOverrun="CLIP" underline="true"><br></br>         <font><br></br>            <Font size="24.0" /><br></br>         </font><br></br>      </Label><br></br>      <Button fx:id="greetMeButton" layoutX="30.0" layoutY="78.0" mnemonicParsing="false" text="Grüße Mich" /><br></br>      <Button layoutX="137.0" layoutY="78.0" mnemonicParsing="false" onAction="#closeApplicationAction" text="Beenden" /><br></br>   </children><br></br></Pane>

Wichtige Elemente sind dabei:

<Pane ... fx:controller="helloworld.HelloWorldController">

Wir definieren in der Pane, welche Klasse als Controller dienen soll. Eine Instanz dieser Klasse wird vom FXMLLoader erzeugt.

<Button fx:id="greetMeButton" ... />

Wir geben dem Button eine feste Id, um dann über den FXMLLoader eine Referenz zu dem Control bekommen zu können.

Wichtig: Wie schon mehrfach gesagt: Dieses Vorgehen ist suboptimal und von mir nicht empfohlen.

<Button ... onAction="#closeApplicationAction" ... />

Wir definieren in der fxml Datei, welche Methode im Controller bei der Action ausgeführt werden soll.

Links

Edit

  • 2020-07-03 Wechsel zu einem Repository für die ganze Serie.