Java Schnellstart mit IntelliJ IDEA

In dem Blog Beitrag JavaFX: Installation habe ich die Installation von Java behandelt, welches die wichtige Abhängigkeit ist, um mit JavaFX oder mit den Build Tools Maven oder Gradle zu entwickeln.

Es geht prinzipiell aber noch deutlich einfacher, denn die Entwicklungsumgebung IntelliJ IDEA, welche frei in der Community Edition verwendbar ist, bietet eine sehr gute Verwaltung von den JDKs.

Aus dem Grund kann auf die manuelle Installation eines JDKs verzichtet werden. Es reicht aus, IntelliJ IDEA zu installieren um dann innerhalb der Entwicklungsumgebung die JDKs zu verwalten.

In dem Video auf YouTube zeige ich, wie die JDKs verwaltet werden können, sowohl bei Anlage von neuen Projekten als auch beim Öffnen von bestehenden Projekten.

Links

JavaFX: Installation

Eine kleine Übersicht über die notwendigen Abhängigkeiten von JavaFX – das sich zum Glück rein auf die Abhängigkeit zu Java reduzieren lässt, so dass es in diesem Blog nach minimaler Einstufung von JavaFX in erster Linie um die Installation der „richtigen“ Java Version geht.

Teile der JavaFX Serie (aktualisiert)

Teile der JavaFX Serie (alt / geplant)

Code der JavaFX Serie

Der Source Code sowie alle Dokumente finden sich auf GitHub: https://github.com/kneitzel/blog-javafx-series

Übersicht über JavaFX

JavaFX ist ein modernes Framework zur Erstellung von graphischen Oberflächen in Java. JavaFX ist eine eigenständige Komponente, die oft nicht im Java Development Kit integriert ist. Bei Oracle / OpenJDK war JavaFX in den Downloads ab Java SE 7 Update 2 bis einschließlich Version 10. Ab Version 11 war dies nicht mehr dabei. Es gibt aber teilweise Anbieter, die auch aktuelle OpenJDK Versionen mit integriertem JavaFX anbieten.

Unterschied zu Swing

Ein wichtiger Unterschied zu Swing ist, dass eine Oberfläche in einem separaten File beschrieben werden kann, statt diese in Java selbst zusammen zu stellen (deklarative Oberfläche im Gegensatz zu der programmierten Oberfläche).

Des Weiteren bietet JavaFX einige technische Aspekte wie z.B. das Binding von Controls an Modelle.

Installation

Da es nicht mehr fester Bestandteil von Java ist, sind manuelle Aktivitäten notwendig. Dazu gibt es zwei Möglichkeiten:

Eigenständige Installation

Es zwei Möglichkeiten für eine Installation. Zum einen kann auf ein JDK zurückgegriffen werden, in dem das JavaFX noch eingebunden ist. Dies ist sehr einfach, aber alle Entwickler am Projekt müssen genau solch eine Installation wählen.

Oder das JavaFX wird eigenständig installiert. Damit dies aber gefunden wird, muss der Ort konfiguriert werden, indem ein Module Path gesetzt wird. Dies erfordert Angaben bei in der Entwicklungsumgebung oder auf der Kommandozeile, die dann auch jeder Entwickler machen muss.

Wenn dieser Weg gewählt wird, dann kann auch ein JDK mit eingebautem JavaFX benutzt werden. Dann ist keine komplexe Konfiguration notwendig, aber die Entwickler sind auf eines der eher raren Angebote angewiesen. Ein solches JDK findet sich u.a. auf https://bell-sw.com/.

Gradle / Maven Build Tool

JavaFX kann als Abhängigkeit in den Build Tools Gradle oder Maven eingetragen werden. Neben den Abhängigkeiten sind auch noch ein paar weitere Einträge notwendig, aber diese sind für alle Entwickler an einem Projekt gleich. Die Entwickler müssen auf ihren Computern keine weiteren Dinge installieren.

Dies ist die Methode, welche ich im Rahmen der JavaFX Serie nutzen werde und euch im Detail vorstellen werde. Dabei werde ich mich vor allem auf Gradle konzentrieren, aber ich werde auch ein Maven Projekt kurz vorstellen.

Hinweis
Bei den Build Tools ist in der Regel keine systemweite Installation notwendig. Statt dessen wird ein sogenannter Wrapper im Projekt integriert, der die korrekte Version des Build Tools im Projektverzeichnis zur Verfügung stellt.

Java Version

Bei der Auswahl der richtigen Java Installation sind ein paar Punkte zu beachten.

JRE vs. JDK

Generell wird bei Java zwischen dem Runtime Environment (JRE) und dem Development Kit (JDK) unterschieden.

Um Java Programme auszuführen wird nur ein JRE benötigt. Das JDK enthält neben einem JRE noch alle Komponenten, die der Entwicklung von Java Programmen dienen, z.B. den Java Compiler.

Wichtig
Wir benötigen somit auf jeden Fall ein JDK.
Achtung
Oracle bietet auf http://java.com ein altes JRE an. Dieses sollte man nicht installieren! Es wird nicht benötigt und neigt eher dazu, Probleme zu bereiten!

Java vs. OpenJDK

Es gibt das Produkt Java von Oracle, welches unter einer speziellen Lizenz von Oracle steht und frei heruntergeladen werden kann. Des Weiteren gibt es das OpenJDK, welches die open source Variante ist und einem vollwertigen Java entspricht und das auch von vielen Firmen aktiv weiterentwickelt wird. (z.B. IBM, Azul, …)

Tipp
Die OpenJDK Lizenz gibt einem deutlich mehr Freiheiten und birgt bei der Nutzung deutlich geringere Risiken einer Lizenzverletzung. Oracle Java lässt sich meist frei nutzen, aber es ist die Lizenz zu prüfen um sicher zu gehen, dass die Nutzung abgedeckt ist und keine kommerzielle Lizenz erworben werden muss!

LTS vs. aktueller Version

Java wird aktiv weiterentwickelt und es kommen regelmäßig neue Versionen. Aktuell sind wir zum Zeitpunkt dieser Bearbeitung bei Java 15. Sobald eine neue Version herausgegeben wurde, wird die alte Version in der Regel nicht mehr aktualisiert. Für Security Updates ist es dann notwendig, dass die neue Version installiert wird – was leider auch zu Kompatibilitätsproblemen führen kann.

Diese Problematik wird gelöst, indem sogenannte Long Time Support (LTS) Version bereitgestellt werden. Diese Versionen werden deutlich länger mit Updates versorgt und verringern die Wahrscheinlichkeit, dass es bei dem Installieren eines neueren Builds der LTS Version zu Problem kommt. Derzeit werden die LTS Version Java 8 und Java 11 bereitgestellt.

Diverse JVM

Es gibt teilweise unterschiedliche Java Virtual Machines (JVM). Die original JVM ist die HotSpot JVM von Oracle. Die Eclipse Foundation hat die OpenJ9 JVM entwickelt, die mehr Wert auf schnelle Startzeit und geringen Speicherverbrauch legt. Bei AdoptOpenJDK kann zwischen den beiden Varianten gewählt werden. Da beide universell einsetzbar sein, ist dies eine Wahl, bei der man nicht viel falsch machen kann.

Empfehlung
Ich rate zu der Version 11 eines OpenJDK. Unter Windows / macOS kann auf ein JDK von https://adoptopenjdk.net zurück gegriffen werden.
Unter Linux wird einfach das OpenJDK aus dem Repository verwendet, z.B. mittels
sudo apt-get install openjdk-11-jdk

Links

  • JavaFX / openjfx Homepage: https://openjfx.io/
  • Java OpenJDK AdoptOpenJDK
  • Entwicklungsumgebungen: JetBrains IntelliJ Community, Eclipse, Netbeans
  • Repository mit Code für diese JavaFX Blog Serie: https://github.com/kneitzel/blog-javafx-series

Änderungsnachweis

  • 2020-11-16 Einfügen YouTube Video
  • 2020-10-29 Komplette Überarbeitung
  • 2020-07-03 Wechsel zu einem Repository für die ganze Serie.

JavaFX: Übersicht / Planung

Teile der JavaFX Serie (aktualisiert)

Teile der JavaFX Serie (alt / geplant)

Code der JavaFX Serie

Der Source Code sowie alle Dokumente finden sich auf GitHub: https://github.com/kneitzel/blog-javafx-series

Überblick

In dieser Serie zu JavaFX möchte ich einige Punkte der JavaFX Entwicklung aufgreifen und behandeln.

Nachdem ich einen ersten Ansatz gestartet hatte, war ich mit dem Ergebnis unzufrieden und ich habe mir Alternativen überlegt. Es gibt einige Veränderungen an der Serie, denn ich werde zukünftig dreigleisig fahren:

  1. Blog Beiträge. Diese sind sowohl im Web als auch als Word Dokument auf Github zu finden.
  2. Word / PDF Dokument mit gesammeltem Wissen – gibt in etwa die Blog Inhalte wieder, aber ggf. umstrukturiert. Die Inhalte sind auch nicht identisch.
  3. YouTube Videos – Ich erstelle zu einzelnen Blogs YouTube Videos, in denen ich versuche, die Punkte etwas zu zeigen und zu erläutern.

Vorhandene Inhalte

Übersicht / Planung

In diesem Abschnitt gebe ich einen Überblick über die Serie und das Format.

Installation

Hier wird nur ein kleiner Überblick gegeben und Hinweis zu einer Java Installation gegeben.

Die ursprünglich enthaltene Einführung in Gradle hat einen eigenständigen Eintrag erhalten.

Maven & Gradle

Übersicht über die Nutzung von JavaFX mit Maven und Gradle. Erläuterung des Beispiel-Projekts.

Scene Builder

Der Scene Builder ist ein GUI Editor, mit dem man auf einfache Art und Weise eine Oberfläche zusammen klicken kann. Diese ist ein separates Tool und ist damit das Mittel meiner Wahl, da es die Aufwände klein hält und eigentlich (fast) keine Stolpersteine bereithält.

Erläuterung der Applikation

Ich habe in den ersten Teilen einfach Code präsentiert, ohne dies näher zu erläutern (um die Beiträge fokussiert auf das eigentliche Thema zu halten). Das hole ich hier nach.
(Wird bei der Nachbearbeitung entfallen und in Scene Builder mit aufgehen!)

Model / View / Controller

JavaFX verfolgt ein MVC Konzept. Dies findet sich leider in den meisten Applikationen nicht wieder. Dies soll ein einfacher kleiner Einstieg in die MVC Entwicklung darstellen und ich zeige auf, was für Probleme ich sehe und mit welchen Mitteln diese ggf. gelöst werden könnten. Leider mit dem Resümee, dass ich MVC mit JavaFX eher ablehne.

MVVM

Da ich das MVC Patten eher ablehne präsentiere ich hier eine Lösung, die ich für mich als gut empfinde und ich die weiterempfehlen würde.

Komplette MVVM Applikation

Beschreibung eine kompletten MVVM Applikation, um einige Punkte zu demonstrieren. Die Applikation wird eine kleine Adressverwaltung sein, so dass man das Öffnen und Schließen eines Fensters sowie das saubere Binden an Properties sehen kann.

Layouts

JavaFX bietet viele Möglichkeiten, Layouts zu gestalten. Diese möchte ich etwas erläutern, um zu verdeutlichen, dass man kein Scene Builder braucht, um Oberflächen zu gestallten. Dazu stelle ich die einzelnen Container vor (HBox, VBox und diverse Panes)

Formulare einfach erstellen

Es gibt zu JavaFX interessante Erweiterungen. Eine Erweiterung dient der einfachen Erstellung von Formularen und diese Erweiterung möchte ich vorstellen.

Multi Platform

Es gibt die Möglichkeit, JavaFX Applikationen auf vielen Plattformen auszuführen. Diese Möglichkeiten werden in diesem Blog Beitrag erläutert und es wird eine Übersicht über die Möglichkeiten gegeben.

Es fehlt Dir ein Punkt, der Dich bezüglich JavaFX interessiert? Schreib mir einen Kommentar hier im Blog oder schreib mir im Java-Forum: Dort bin ich als kneitzel unterwegs. Natürlich kannst Du mich auch per Email erreichen unter: konrad@kneitzel.de

Links

Edit

  • 2020-11-08 Einbettung des YouTube Videos
  • 2020-11-02 Diverse Korrekturen und Verbesserungen
  • 2020-10-29 Komplette Überarbeitung
  • 2020-07-03 Wechsel zu einem Repository für die ganze Serie.
  • 2020-06-29 MVVM veröffentlicht und komplette MVVM Applikation als weiteren Punkt eingeschoben.

JavaFX: MVVM

JavaFX Serie

Wir haben uns im letzten Beitrag etwas mit der Aufteilung in Model / View / Controller beschäftigt und mussten in der View für das Binding teilweise auf Code ausweichen. Dies haben wir in Form von kleinen JavaScript Elementen eingefügt, was natürlich nicht unbedingt optimal ist.

Besonders schwerwiegend war, dass wir uns vom SceneBuilder verabschiedet haben, der hier mit gewissen Teilen Probleme bekommen hat.

Um diese Problematik aufzulösen, kommen wir automatisch zu einem neuen Pattern: Aus dem Model View Controller (MVC) Pattern wird ein Model – View – View Model (MVVM) Pattern.

Die erste wichtige Veränderung: Unsere View wird aus zwei Bestandteilen bestehen: Wie gewohnt das fxml aber es kommt auch eine Java Datei hinzu. Dies macht Sinn, denn wir haben bereits festgestellt, dass wir Code in der View benötigen und daher können wir dies in eine separate java Datei packen.

Desweiteren bekommen wir ein View Model. Für unser Binding brauchten wir in den vorhergehenden Beiträgen ein Model, welches mit JavaFX spezifischen Properties arbeitete. Wenn wir aber Software Entwickeln, dann hatten unsere Models (hoffentlich) statt z.B. (Simple)StringProperties immer Strings. Wir hatten hier also immer einen massiven Mehraufwand und den können wir jetzt gezielt abbilden: Wir haben ein dediziertes View Model. In diesem verwenden wir dann unser Model, das im Rahmen der „normalen Java Entwicklung“ entstanden ist.

Library für MVVM

Rund um MVVM gibt es sehr viel Dokumentation. Microsoft hat sehr stark auf MVVM zugegriffen z.B. in seinem Windows Presentation Framework (WPF). Ein Projekt hat dies auf JavaFX adaptiert: mvvmFX. Im Wiki finden sich zu MVVM auch gute Links, die das Thema sehr ausführlich beschreiben und ich kann nur raten, dies im Detail zu lesen um ein tiefes Verständnis zu bekommen.

Anpassen unseres Projekts

Solltest Du der Serie nicht gefolgt sein: Der Code der ganzen JavaFX Serie findet sich unter https://github.com/kneitzel/blog-javafx-series. Der MVC Code in „04 helloworld-mvc“ und die mvvm Version in „05 helloworld-mvvm“

Abhängigkeit

Wir wollen die mvvmFX Library nutzen, daher tragen wir diese als Abhängigkeit in unsere build.gradle Datei ein, indem wir in dem dependency Block einfügen:

compile group: 'de.saxsys', name: 'mvvmfx', version: '1.8.0'

Neue Klasse: helloworld.entity.Greeting

Im Namespace helloworld legen wir eine neue Klasse an: entity.Greeting, in der wir unsere Business Logik / Daten für eine Begrüßung hinterlegen:

package helloworld.entity;

public class Greeting {
private String greeting;

public Greeting() {
greeting = "Hallo Welt!";
}

public Greeting(final String name) {
greetName(name);
}

public String getGreeting() { return greeting; }

public void greetName(final String name) {
if (name == null || name.isEmpty()) {
greeting = "Hallo Welt!";
} else {
greeting = "Hallo " + name + "!";
}
}
}

Dies ist eine einfache Klasse mit Daten und Business Logik, ganz ohne irgend welche JavaFX Spezialitäten wie StringProperties und Co. Die Klasse wirkt von Design etwas gekünstelt, aber wir nehmen dies an dieser Stelle einfach so hin.

Umbenennen von Dateien

Nun wollen wir einige Dateien umbenennen:

a) View

Zu der View gehören ab jetzt das fxml File und das bisherige Controller File. Daher benennen wir diese um in HelloWorldView(.java|.fxml)

b) ViewModel

Das bisherige Model wird zum ViewModel, daher benennen wir die Datei um in HelloWorldViewModel.java

Das Umbenennen machen wir mit dem Refactoring der IDE, damit es immer gleich an allen Stellen geändert ist.

Anpassungen der View

Als erstes passen wir die HelloWorldView.fxml an:

  • Wir löschen die Referenz auf JavaScript <?language javascript?>
  • Das Label mit dem Greeting bekommt ein fx:id=“greetingLabel“ und das Binding für text wird gelöscht (text=“${controller.model.greeting}“)
  • Das fx:script wird komplett entfernt.

Dann passen wir die HelloWorldView.java an:

package helloworld;

import de.saxsys.mvvmfx.FxmlView;
import de.saxsys.mvvmfx.InjectViewModel;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;

import java.net.URL;
import java.util.ResourceBundle;

public class HelloWorldView implements FxmlView<HelloWorldViewModel>, Initializable {

/**
* Model with our data.
*/
@InjectViewModel
private HelloWorldViewModel model;


/**
* TextField for name.
*/
@FXML private TextField nameTextField;

/**
* Label for greeting
*/
@FXML private Label greetingLabel;

/**
* Close application.
* @param e ActionEvent (unused).
*/
public void closeApplicationAction(final ActionEvent e) {
model.closeApplication();
}

/**
* Updates Greeting in model.
* @param e ActionEvent (unused).
*/
public void updateGreeting(final ActionEvent e) { model.updateGreeting();}

@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
nameTextField.textProperty().bindBidirectional(model.nameProperty);
greetingLabel.textProperty().bind(model.greetingProperty);
}
}

Die folgenden wichtigen Änderungen sind sichtbar:

  • Es wird nun FxmlView<HelloWorldViewModel>, Initializable implementiert.
  • Das Model wird nicht mehr selbst erzeugt sondern kommt per Injection.
  • Die Controls sind nun in der View bekannt (greetingLabel und nameTextField).
  • Die Business-Logik (Beenden der Applikation) ist in das ViewModel gewandert.
  • Das Binding ist nun in der initialize Methode.

Die ViewModel Klasse wird auch angepasst:

package helloworld;

import de.saxsys.mvvmfx.ViewModel;
import helloworld.entity.Greeting;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

/**
* Our Model for Hello World.
*/
public class HelloWorldViewModel implements ViewModel {

/**
* Our Model to use.
*/
private Greeting greeting;

/**
* Greeting to show.
*/
public StringProperty greetingProperty = new SimpleStringProperty();
public StringProperty nameProperty = new SimpleStringProperty("Bitte Namen eingeben");

public HelloWorldViewModel() {
greeting = new Greeting("Welt");
updateViewModel();
}

/**
* Updates the greeting to use the name given.
*/
public void updateGreeting() {
if (nameProperty.get().length() > 0) {
greeting.greetName(nameProperty.get());
nameProperty.setValue("Bitte Namen eingeben");
} else {
greeting.greetName("Welt");
}

updateViewModel();
}

/**
* Updates the ViewModel when Model was changed.
*/
protected void updateViewModel() {
greetingProperty.setValue(greeting.getGreeting());
}

/**
* Close application.
*/
public void closeApplication() {
System.exit(0);
}
}

Die wichtigen Änderungen sind nun:

  • die Klasse implementiert ViewModel
  • closeApplication als Business Logik ist dazu gekommen.
  • Die Greeting Entity wird verwendet. Wir haben jedoch unseren eigenen Code geschrieben, um Änderungen an den Properties auszulesen / zu setzen.

Unsere Applikation muss auch geändert werden:

package helloworld;

import de.saxsys.mvvmfx.FluentViewLoader;
import de.saxsys.mvvmfx.ViewTuple;
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(final String[] args) {
launch(args);
}

@Override
public void start(final Stage primaryStage) throws IOException {
primaryStage.setTitle("Hello World Application");
ViewTuple<HelloWorldView, HelloWorldViewModel> viewTuple = FluentViewLoader.fxmlView(HelloWorldView.class).load();
Parent root = viewTuple.getView();
primaryStage.setScene(new Scene(root));
primaryStage.show();
}

}

Der Aufruf einer MVVM Applikation sieht nun leicht verändert aus. So müssen wir ein ViewTupel erzeugen und nutzen dazu einen neuen, speziellen FluentViewLoader. 

Damit wäre die Applikation erst einmal umgestellt auf das MVVM Pattern mit Hilfe der mvvmFX Library. 

Links

Edit

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

 

JavaFX: Model / View / Controller

JavaFX Serie

Wir haben bisher den Controller und die View gesehen, konnten Aktionen durchführen, aber wir haben bisher noch nicht mit Daten gearbeitet.

HelloWorldModel

Die Daten liegen in einem Model vor. Dazu erstellen wir uns nun erst einmal eine eigene Klasse: HelloWorldModel

package helloworld;

/**
* Our Model for Hello World.
*/
public class HelloWorldModel {

}

Nun können wir ein erstes Element hinzu fügen: Unser Greeting. Ein Binding setzt voraus, dass man informiert werden kann, wenn sich Werte ändern. Dazu existieren in JavaFX diverse Property Klassen.

Für String Werte gibt es StringProperty und SimpleStringProperty, welche wir nutzen können.

So erstellen wir uns eine Variable greeting:

private StringProperty greeting = new SimpleStringProperty();

Wenn der Controller Werte lesen können soll, dann wird ein Getter benötigt:

public String getGreeting() { return greeting.get(); }

Für Schreibzugriffe wird der Setter benötigt:

public void setGreeting(final String greeting) { this.greeting.setValue(greeting); }

Und falls die Oberfläche Änderungen mitbekommen soll, so muss das Property selbst bekannt gemacht werden:

public StringProperty greetingProperty() { return greeting; }

Nach dem gleichen Schema können wir nun auch noch den Namen hinzu fügen sowie eine Methode, die das Greeting ändert, wenn der Name geändert wurde:

package helloworld;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

/**
* Our Model for Hello World.
*/
public class HelloWorldModel {

private StringProperty name = new SimpleStringProperty();

public void setName(final String name) {
this.name.setValue(name);
}

public String getName() { return name.get(); }

public StringProperty nameProperty() { return name; }

private StringProperty greeting = new SimpleStringProperty();

public void setGreeting(final String greeting) {
this.greeting.setValue(greeting);
}

public String getGreeting() { return greeting.get(); }

public StringProperty greetingProperty() { return greeting; }

public HelloWorldModel() {
setGreeting("Hallo Welt!");
}

public void updateGreeting() {
if (name.get().length() > 0) {
greeting.setValue("Hallo " + name.get() + "!");
name.setValue("Bitte Namen eingeben");
} else {
greeting.setValue("Hallo Welt!");
}
}
}

Damit hätten wir ein fertiges Model, welches wir nutzen können.

HelloWorldController

Den Controller können wir nun noch entsprechend aufbauen:

  • Wir benötigen eine Instanz vom Model sowie ein Getter, damit das Model gelesen werden kann.
  • Wir benötigen die Behandlung von 2 Actions: Einmal zum Beenden der Applikation und zum Anderen um das Greeting anpassen zu können.
package helloworld;

import javafx.event.ActionEvent;

public class HelloWorldController {

/**
* Model with our data.
*/
private HelloWorldModel model = new HelloWorldModel();

/**
* Gets our model.
* @return Our model.
*/
public HelloWorldModel getModel() { return model; }

/**
* Close application.
* @param e ActionEvent (unused).
*/
public void closeApplicationAction(final ActionEvent e) {
System.exit(0);
}

/**
* Updates Greeting in model.
* @param e ActionEvent (unused).
*/
public void updateGreeting(final ActionEvent e) { model.updateGreeting();}
}

HelloWorld.fxml

Nun fehlt zuletzt nur noch die View.

In der View benötigen wir ein Label für unser Greeting, ein Eingabefeld für den Namen und zwei Buttons: Einmal zum Aktualisieren und zum Anderen um die Applikation zu beenden.

Bei mir sieht dies auf die Schnelle so aus:

Achtung: Bezüglich Bindings unterstützt der SceneBuilder leider nicht, was wir hier benötigen. Daher ist es nach der rein optischen Erstellung dieser Maske leider Zeit, uns vom SceneBuilder wieder zu verabschieden (Für diesen Blog-Beitrag – wir benötigen diesen ab dem nächsten Beitrag wieder). Wir werden später auch weitere Möglichkeiten lernen, wie wir Fenster schnell und sauber ohne GUI Editor erstellen können, die dann auch in der Größe veränderbar sind und gut aussehen. Oder wie wir schnell und einfach Formulare erstellen können.

Somit schließen wir den SceneBuilder und editieren nur noch das Textfile direkt z.B. in IntelliJ.

Als erstes binden wir das Label an unsere greeting Property in unserem Model:

<Label ... text="${controller.model.greeting}" ... >

Über die ${} können wir ein Binding erstellen. Wichtig ist, dass der Controller gesetzt ist so dass wir auf das model zugreifen können (Getter muss da sein!) und dann auf die Property greeting zugreifen können (Getter und greetingProperty müssen da sein!).

Die Buttons binden wir an unsere Methoden im Controller:

<Button ... onAction="#closeApplicationAction" text="Beenden" />
<Button ... onAction="#updateGreeting" text="Aktualisiere" />

Nun fehlt nur noch das Binding des TextFields. Hier laufen wir in ein kleines Problem, wenn wir benötigen ein Bidirectionales Binding, denn wir wollen den Text ja verändern können. Das ist leider etwas, das derzeit nicht im fxml möglich ist. In Zukunft ist dies vorgesehen über die # statt dem $.

Nun haben wir Code, den wir nicht in den Controller packen wollen, also müssen wir etwas in die Trickkiste greifen:

Wir können Scripts mit in die fxml Datei packen. Dazu müssen wir als erstes im Kopf der Datei die Sprache angeben, die wir verwenden wollen:

<?language javascript?>

Nun können wir das TextField definieren ohne jedes Binding aber mit einer fx:id:

<TextField fx:id="nameTextField" ... text="Bitte Namen eingeben." />

Nun benötigen wir Code, der ein bidirectional Binding aufbaut. Dazu müssen wir in der Klasse Bindings die Methode bindBidirectional aufrufen und dabei die Property und das Control angeben:

<fx:script>
javafx.beans.binding.Bindings.bindBidirectional(
controller.model.nameProperty(),
nameTextField.textProperty()
);
</fx:script>

Mit diesem Trick haben wir nun auch das Binding in beide Richtungen erstellt und es spricht nichts dagegen, die Applikation einmal zu starten und dann einen Namen anzugeben und zu staunen, wie das Greeting sich ändert, wenn man den Knopf drückt und erneut der Text erscheint: „Bitte Name eingeben“.

Der Code des ganzen Projekts findet sich unter https://github.com/kneitzel/blog-javafx-series im Verzeichnis „04 helloworld-mvc“

Links

Edit

  • 2020-07-03: Wechsel zu einem Repository für die ganze Serie.
  • 2020-06-29: Den Part bezüglich SceneEditor etwas überarbeitet – wir werden diesen in späteren Blog Beiträgen wieder nutzen (können). 

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;

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();
}

}

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) {
launch(args);
}

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
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;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;

public class HelloWorldController {

@FXML private Button greetMeButton;

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

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

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

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
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"?>

<?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" fx:controller="helloworld.HelloWorldController">
<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 fx:id="greetMeButton" layoutX="30.0" layoutY="78.0" mnemonicParsing="false" text="Grüße Mich" />
<Button layoutX="137.0" layoutY="78.0" mnemonicParsing="false" onAction="#closeApplicationAction" text="Beenden" />
</children>
</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.

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.