JavaFX: Scene Builder

JavaFX Serie

Als einen wichtigen Unterschied zu Swing habe ich angegeben, dass die Oberflächen deklarativ erstellt werden können. In dem HelloWorld fand sich aber dann davon aber nichts und das einfache Fenster habe ich im Programmcode erstellt.

Dies ändert sich nun und dazu besorgen wir uns noch ein zusätzliches Tool: Den Scene Builder von Gluon. Nach der Installation können wir den Scene Builder auch in IntelliJ integrieren, indem wir in den Settings unter Language & FRameworks -> JavaFX den Pfad zum Scene Builder eintragen.

Eine erste Oberfläche erstellen

Wir erstellen unsere erste Oberfläche, in dem wir einfach in Screen Builder ein neues, leeres Dokument erstellen.

Als erstes ziehen wir aus den Containers ein Pane in unser Dokument. Dieses Element wird alle weiteren Elemente beherbergen.

Als nächstes Ziehen wir einen Label aus den Controls auf unser Pane. Damit können wir jetzt etwas spielen:

  • Wir klicken abwechselnd auf Label oder das Pane: Das angeklickte Element wird markiert und es werden die Properties angezeigt.
  • Wir verschieben die Elemente, ändern am Rand die Größe….
  • Wir ändern Properties. Beim Label können wir den Text ändern und wie der Text dargestellt wird.

Das Label platzieren wir oben im Pane, und verbreitern es, dass es über die ganze Breite geht. Der Text soll „Hallo Welt“ sein, mit 24px Größe und unterstrichen. Und der „Node“ des Label soll ein Alignment CENTER haben.

Unterhalb des Label können wir nun noch zwei Buttons hin ziehen. Diese bekommen den Text „Grüße Mich“ und „Beenden“.

Wir können die alles nun mit der Maus etwas verschieben und die Größe ändern, bis es uns gefällt.

Mein Ergebnis:

Unsere erstellte Oberfläche speichern wir nun als HelloWorld.fxml im Verzeichnis src/main/resources/helloworld/ unseres Testprojekts. Das helloworld Verzeichnis bitte so anlegen.

Hinweis: Hier wird die Trennung deutlich: Unser Java-Code liegt unter src/main/java, aber alle Resourcen wie Bilder oder so eine Scene von JavaFX landen in src/main/resources.

Inhalt der Datei

Die erstellte Datei können wir uns mit einem Texteditor ansehen. Es handelt sich um eine XML Datei und wir können die Struktur, die wir aufgebaut haben sowie die Einstellungen wieder finden.

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.text.Font?>


<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">
<children>
<Label alignment="CENTER" contentDisplay="CENTER" layoutX="-1.0" layoutY="14.0" prefHeight="53.0" prefWidth="237.0" text="Hallo Welt" textOverrun="CLIP" underline="true">
<font>
<Font size="24.0" />
</font>
</Label>
<Button layoutX="30.0" layoutY="78.0" mnemonicParsing="false" text="Grüße Mich" />
<Button layoutX="137.0" layoutY="78.0" mnemonicParsing="false" text="Beenden" />
</children>
</Pane>

Damit aber diese Scene von unserem Programm verwendet werden kann, müssen wir natürlich noch unser Programm anpassen:

package helloworld;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.io.IOException;

public class HelloWorld extends Application {

public static void main(String[] args) {
launch(args);
}

@Override
public void start(Stage primaryStage) throws IOException {
Parent root = FXMLLoader.load(getClass().getResource("HelloWorld.fxml"));
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();

}
}

Damit haben wir ein erstes kleines Programm. Die Befehle werde ich später im Detail erläutern. Jetzt nur als groben Überblick: Es wird die fxml Datei geladen und dann daraus ein Applikationsfenster angepasst. Wenn wir es starten, sollte ein Fenster mit dem erstellten Inhalt angezeigt werden, aber noch passiert da nichts, wenn wir einen Button drücken.

Aktionen in SceneBuilder definieren

Eine Möglichkeit ist, dass wir Aktionen in der fxml Datei hinterlegen.

Dazu erstellen wir im ersten Schritt eine neue Klasse HelloWorldController mit einer Methode, die das Programm beenden soll, wenn eine „Action“ statt findet:

package helloworld;

import javafx.event.ActionEvent;

public class HelloWorldController {

public void closeApplicationAction(ActionEvent e) {
System.exit(0);
}
}

IntelliJ zeigt den Klassen- und Methodennamen grau an, um uns zu zeigen: Dies wird bisher nicht benutzt.

Als nächstes müssen wir im SceneBuilder die Aktion hinterlegen. Dazu sind zwei Schritte notwendig:

a) Wir hinterlegen in der Scene, welcher Controller zuständig ist. Dazu müssen wir im linken Bereich unten auf Controller klicken um dann die Controller class auszuwählen. Dort geben wir helloworld.HelloWorldController ein.

b) Im nächsten Schritt wählen wir den Knopf und klicken im rechten Bereich auf Code : Button. Dort finden wir unter Main ein Feld On Action. Dort geben wir closeApplicationAction ein.

Wir können die Scene nun speichern und zum IntelliJ zurück wechseln. IntelliJ sollte diese Änderung mitbekommen haben und nun die Klasse und Methode nicht mehr grau darstellen.

Nun einfach einmal die Applikation starten und prüfen, ob wir über unseren Beenden Knopf die Applikation beenden können.

Wir haben hier etwas aufgebaut, was eine sehr schöne Trennung aufzeigt:

  • Under Controller bietet eine Action an. Ob und wie diese aufgerufen wird, ist erst einmal egal. Das kein Button sein aber auch irgend ein anderes Control, dass ein ActionEvent auslösen kann.
  • Die UI lässt sich jederzeit anpassen. Egal, was da benutzt wird: Die Methoden des Controllers stehen zur Verfügung

Wir haben hier also eine sehr gute Trennung zwischen Controller und View.

Eine so strickte Trennung sollte wenn möglich immer eingehalten werden. Bei dem „Güße Mich“ Button möchte ich aber auch einmal kurz aufzeigen, was man oft bei anderen Entwicklern an Code sieht.

In dem SceneBuilder gehen wir auf den „Grüße Mich“ Knopf und tragen unter Code : Button eine fx:id ein: greetMeButton. Damit vergeben wir eine klare ID an diesen Button, so dass wir einfach auf diesen zugreifen können.

In unserem Controller fügen wir nun ein:

a) Die Methode, die uns grüßen soll:

public void handleGreetMeButton(ActionEvent e) {
Alert alert = new Alert(Alert.AlertType.INFORMATION, "Hallo Anwender!", ButtonType.OK);
alert.showAndWait();
}

b) Eine Instanzvariable hinzu mit Annotation @FXML:

@FXML private Button greetMeButton;

c) Eine Initialisierungs-Methode

@FXML
public void initialize() {
greetMeButton.setOnAction(this::handleGreetMeButton);
}

Wenn wir dies ausführen, dann sehen wir, dass alles funktioniert. Aber jetzt haben wir eine klare Abhängigkeit von dem Controller hin zur View. Es muss ein Button mit fx:id greetMeButton geben. Wenn jemand anderes es Oberfläche baut oder anpasst, dann hat er hier eine klare Einschränkung.

Und im Controller sind wir nicht mehr auf Funktionen konzentriert sondern auf konkrete Bedienelemente. Der Entwickler sollte sich aber in erster Linie um Funktionen auf Basis des Modells (Dazu kommen wir später in der Serie) kümmern.

Evtl. ein kleiner Vergleich: Ein Motor im Auto hat eine Steuerung „Gas“. Da kann man einen Wert setzen. Woher dieser Wert kommt (Gaspedal, Gashebel, Computer, Joystick, ….) ist egal. 

Daher dieser Abschnitt nur als kurzer Hinweis. Wir lassen dies jetzt auch noch so, denn im nächsten Beitrag werde ich die Applikation im Detail erläutern.

Die Sourcen dieses Projektes finden sich unter https://github.com/kneitzel/blog-javafx-series in „02 helloworld-fxml“

Links

Edit

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

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.