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: IntelliJ

Ein schneller, kurzer Überblick über IntelliJ. Dies ist meine bevorzugte Entwicklungsumgebung daher möchte ich diese kurz Vorstellen.

Dies ist ein kurzer Blog – das Hauptgewicht liegt hier auf dem YouTube Video.

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

Versionen

IntelliJ IDEA, oft einfach IntelliJ genannt, wird in zwei Versionen angeboten: Der freien Community Edition und der kostenpflichtigen Ultimate Edition.

Community Edition

Die Community Edition ist komplett frei verwendbar und eine voll funktionstüchtige Entwicklungsumgebung. Lediglich gewisse Enterprise Funktionen sind nicht integriert, was in meinen Augen für Anfänger eher gut ist, da die Entwicklungsumgebung dadurch weniger Elemente bietet, die für den Benutzer uninteressant sind.

Ebenso gefällt mir die Aufgliederung in mehrere Entwicklungsumgebungen. Anders als Eclipse muss IntelliJ nicht alles abdecken. Für C/C++, PHP, JavaScript, … gibt es eigene, spezialisierte Entwicklungsumgebungen. Der Focus liegt bei IntelliJ auf Java – aber ohne darauf komplett beschränkt zu sein! So lässt sich ebenso auch HTML, JavaScript und co komfortabel in IntelliJ bearbeiten…

Die Nutzung beschränkt sich nicht auf nicht gewerbliche Nutzung. Die Community Edition ist auch kommerziell einsetzbar.

Und natürlich lassen sich auch Enterprise Applikationen mit IntelliJ schreiben. Lediglich gewisse Dinge sind dann nicht komfortabel in die IDE integriert.

Ultimate

Die Ultimate Edition wurde um einige Enterprise Features erweitert. So werden viele Elemente aus dem Enterprise Umfeld erkannt und komfortabel in die Oberfläche integriert. Einige Aufgaben lassen sich so mit wenigen Klicks erledigen und die IDE bietet eine erweiterte Unterstützung in diesem Bereich. Daher kann ich diese Version, die ich auch im Einsatz habe, nur empfehlen.

JetBrains bietet die Tools nur im „Abo“ an, wobei nach einem Jahr auch eine dauerhafte Lizenz für die dann aktuelle Version erworben wird und das Abo nicht wie bei Anderen Anbietern wie Adobe, dauerhaft behalten werden muss. Da die Kosten vom ersten bis zum dritten Jahr sinken, kann ich nur empfehlen, das Abo beizubehalten, so man die Version dauerhaft nutzen möchte.

Die Preise für eine individuelle Lizenz sind aus meiner Sicht auch voll gerechtfertigt – für eine individuelle Lizenz für IntelliJ zahlt man derzeit 149€/Jahr und der Preis sinkt bis zum dritten Jahr auf 89€/Jahr. (Bzw. wenn man alle Tools von Jetbrains nutzen möchte: 249€ im ersten Jahr und der Preis sind bis zu 149€ im dritten Jahr.) Verglichen zu anderen Anbietern von (kommerziellen) Entwicklungsumgebungen ist das aus meiner Sicht ein geringer Preis.

Wichtig
Die Ultimate Edition ist nicht notwendig! Es muss kein Geld ausgegeben werden, um in Java zu entwickeln! Die Community Edition ist frei verfügbar und sehr gut. Und sollte diese nicht den Ansprüchen genügen, so kann Eclipse oder Netbeans vielleicht überzeugen.

Installation

Manuelle Installation

Beim Hersteller kann ein Installer von IntelliJ Community Edition heruntergeladen werden. Ein Setup führt einen dann mit wenigen Schritten durch die Installation so dass am Ende IntelliJ installiert ist.

Und der Start ist dann nach der Installation wie gewohnt möglich, d.h. das Setup trägt die Applikation entsprechend ein, so dass es auf Windows, gnome, KDE, … im Startmenü aufgeführt wird.

Wichtig ist: Updates müssen dann auch manuell installiert werden, also erneutes herunterladen, ggf. eine Deinstallation der alten Version und dann de manuelle Installation der neuen Version.

Toolbox

JetBrains bietet ein kleines Programm an, dass sich Toolbox nennt. Dieses sitzt im Systray und prüft regelmäßig, was für neue Versionen verfübar sind.

Es bietet einen einfachen Weg, die Jetbrains Produkte als auch das Android Studio zu installieren, upzudaten oder zu deinstallieren.

Des Weiteren können mehrere Versionen parallel installiert sein. Dies kann im Hintergrund passieren, d.h. bei einem Update wird per Default die alte Version noch nicht deinstalliert. Somit ist bei Problemen ein schneller Wechsel zur alten Version möglich. (Dieses Verhalten lässt sich natürlich konfigurieren, z.B. wenn Speicherplatz knapp sein sollte.)

Die Toolbox kann genutzt werden, die installierte Software zu starten. In den Entwicklungsumgebungen geöffnete Projekte (die dann dort auch beim Start aufgelistet werden, so nicht automatisch das letzte, geöffnete Projekt erneut geöffnet werden soll) ist ebenfalls über den Reiter Projekte direkt startbar.

Erster Start

Konfiguration aus alter Version übernehmen

Wenn die Software das erste mal gestartet wird (es wird für diese Version noch keine Konfiguration gefunden), dann sucht IntelliJ nach früheren Versionen, dessen Konfiguration übernommen werden könnte.

Dann öffnet IntelliJ ein Fenster, in dem es fragt, von welcher Version eine Konfiguration übernommen werden soll. So eine frühere Version gefunden wurde, kann diese ausgewählt werden. Es kann aber auch eine Konfiguration selbst ausgesucht werden – z.B. um eine Vorgabe der Firma oder des Teams direkt zu übernehmen.

Erstkonfiguration

So keine Konfiguration importiert wurde, wird man über einen Konfigurations-Wizard geleitet. Dieser fragt das gewünschte Aussehen, PlugIns und Addons ab, die genutzt werden sollen.

Die Auswahl ist unkritisch, da dies alles jederzeit verändert werden kann. Daher einfach das im Augenblick bevorzugte Aussehen wählen und die Standards bei den weiteren Seiten beibehalten, so dass wir zum eigentlichen Startfenster von IntelliJ kommen.

Startfenster

Wichtig
Dieses Startfenster erscheint nur, wenn es kein zuletzt geöffnetes Projekt gibt (weil erster Start, der Bereich mit den zuletzt geöffneten Projekten ist dann auch nicht vorhanden) oder wenn die Standard Einstellung deaktiviert wurde, so dass nicht mehr das zuletzt geöffnete Projekt geöffnet wird.
Empfehlung
Ich selbst mag es nicht, wenn beim Start das letzte Projekt erneut geöffnet wird, denn ich arbeite zu oft an wechselnden Projekten. Daher ist meine Empfehlung diese Einstellung zu ändern. Diese findet sich in den Settings unter
Appearance & Behaviour -> System Settings im Punkt Reopen Projects on Startup.

IntelliJ-Startfenster

Es finden sich mehrere Bereiche:

  1. Zuletzt geöffnete Projekte
    In der Liste der zuletzt geöffneten Projekte kann man schnell und einfach ein Projekt öffnen.
    Wenn man mit der Maus über ein Projekt geht, dann erscheint am rechten Rand ein X, mit dem ein Projekt aus der Liste entfernt werden kann.
  2. Es kann in dem Menü entweder ein komplett neues Projekt erstellt werden oder ein bestehendes Projekt geöffnet / importiert werden. Dabei kann sogar direkt auf eine Source Verwaltung zugegriffen werden, um das Projekt erst lokal zu holen (z.B. per git von GitHub).
  3. Beim Start überprüft IntelliJ, ob Updates für Plugins verfügbar sind und gibt einen Link mit dem man direkt zu den Plugins kommt mit Sicht auf alle aktualisierbaren Plugins.
  4. In dem Configure Menü kann man verschiedene Konfigurationen aufrufen. Neben den Settings finden sich dort vor allem auch die Plugins. Aber auch viele weitere Einstellungen, auf die ich nicht näher eingehen möchte.

Projekt von SourceVerwaltung öffnen

Startet wir, indem wir einfach ein Projekt von der Sourceverwaltung öffnen. Dazu können wir die Sourcen der Blog Serie als Beispiel nehmen:

  1. Wir wählen „Get from Version Control“ aus.
  2. Im folgenden Fenster können wir die git URL eingeben. Wichtig: Dies ist nicht die Web-URL des Github Projekts, sondern die git Adresse, die man auf der Webseite angezeigt bekommt, wenn man auf den grünen „Code“ Knopf drückt. Für unser Projekt ist dies https://github.com/kneitzel/blog-javafx-series.git.
    Und unter der Adresse können wir den Ordner wählen, in welches die Sourcen kopiert werden sollen, z.B. c:\Projects\javafx-blog
  3. Sollte auf unserem Rechner kein git installiert sein, dann meldet IntelliJ dies und bietet an, git für uns zu installieren.
  4. So wir alles eingegeben haben, können wir mit dem Knopf „clone“ das Herunterladen starten.
  5. Sobald IntelliJ alles heruntergeladen hat, wird der Ordner als Projekt geöffnet. IntelliJ erkennt die Build-Files der Gradle und Maven Projekte und bietet den Import an, welchen wir annehmen sollten.
  6. Nun sollten wir uns erst einmal in Geduld üben: IntelliJ arbeitet nun alle Projekte ab, um diese richtig zu integrieren. Dies dauert einige Minuten und wir sollten IntelliJ diese Zeit geben.

Neues Projekt erstellen

Wenn wir ein neues Projekt erstellen wollen, dann bietet IntelliJ uns eine ganze Reihe an möglichen Projekten.

Wichtig ist, dass wir die Java Version, die open im Fenster eingeblendet ist, richtig setzen. So wir nur ein JDK installiert haben wird IntelliJ dieses korrekt erkannt und oben eingetragen haben. Aber wir können über das Dropdown nicht nur die richtige Version auswählen sondern wir können bereits installierte Versionen hinzufügen und sogar weitere JDKs installieren.

Bezüglich Auswahl des Projekt-Typs ist meine Empfehlung, dass man bei Gradle oder Maven bleibt. So bleibt man bei den Projekten unabhängig und ist nicht an eine spezielle Entwicklungsumgebung gebunden.

Die vielen Möglichkeiten der unterschiedlichen Projekte im Detail zu beschreiben würde den Rahmen des Blog Beitrages sprengen, so dass ich es bei diesem Ratschlag belasse.

Bestehendes Projekt öffnen

Bestehende Projekte lassen sich problemlos öffnen. Dazu muss lediglich der Ordner mit dem Projekt ausgewählt werden und IntelliJ öffnet es für einen.

Links

Änderungsnachweis

  • 2020-11-02 Erste Veröffentlichung und Überarbeitung.

JavaFX: Maven und Gradle

Ein schneller, kurzer Überblick über Maven und Gradle. Dies kann keine Einführung in der Tiefe sein – dazu wäre jeweils eine ganze Blog-Serie notwendig und da ist es sinnvoller, die offizielle Dokumentation zu verwenden!

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

Was sind Maven und Gradle?

Bei Maven und Gradle handelt es sich um Tools, mit denen ein Projekt verwaltet und gebaut werden kann. Ein wichtiges Feature ist dabei die Verwaltung von Abhängigkeiten, die vom Tool automatisch in der gewünschten Version von einem Repository heruntergeladen und bereitgestellt werden.

Dies verringert die Aufwände bezüglich Einrichtung eines Arbeitsplatzes enorm, da die Anzahl der Abhängigkeiten, die installiert werden müssen, minimiert werden.

Vorteile

Der große Vorteil dieser Build Tools ist, dass diese von den gängigen Entwicklungsumgebungen (IntelliJ, Eclipse, Netbeans) unterstützt werden. Damit ist ein Projekt unabhängig von einer Entwicklungsumgebung und jeder Entwickler kann mit der Umgebung arbeiten, die er bevorzugt.

Maven

Übersicht

Maven entstand im Jahr 2003 (Version 1.x) / 2004 (Version 1.0) und kann somit auf eine recht lange Entwicklungszeit zurückblicken.

Das Tool zeichnet sich dadurch aus, dass es ein festen Lebenszyklus vorgibt, der feste Phasen umfasst in dem viele Dinge standardmäßig verankert sind, so dass oft nur wenig Anpassungen notwendig sind.

Folgende Phasen sind in Maven vorgesehen:

archetype

Dient u.a. der Erstellung von Templates, Abhängigkeiten werden aufgelöst und ggf. herunter geladen.

validate

Validierung des Projektes. Ist die Struktur des Projekts ok?

compile

Übersetzen des Codes.

test

Ausführung der automatischen Tests mit Hilfe geeigneter Testframeworks.

package

Die Ergebnisse werden zusammengepackt. Häufig handelt es sich um eine jar Datei.

integration-test

Das erstellte Paket wird an einen anderen Ort kopiert und dort getestet. Dies kann z.B. ein Anwendungsserver sein.

verify

Das erstellte Softwarepaket wird überprüft, um sicher zu stellen, dass die Struktur korrekt ist.

install

Das Softwarepaket wird im lokalen Maven Repository bereitgestellt.

deploy

Das Softwarepaket wird im entfernten Maven Repository bereitgestellt.

Dies kann durch Plugins und Maven Archetypen erweitert und verändert werden, so dass z.B. Docker Container gebaut und verteilt werden können.

Wrapper

Zu Maven gibt es einen Wrapper, der es ermöglicht, dass Maven nicht mehr systemweit auf einem System installiert werden muss. Da unterschiedliche Projekte ggf. unterschiedliche, nicht zueinander kompatible, Versionen von Maven nutzen wollen, ist eine systemweite Installation nicht zielführend.

Stattdessen gibt es ein Script welches die notwendige Version von Maven herunter lädt und innerhalb des Projektverzeichnisses entpackt.

Erzeugung des Wrappers
Es ist möglich, mittels maven ein Wrapper für ein Projekt zu erzeugen. Dazu geht man in das Verzeichnis des Projekts und ruft auf:
„mvn -N io.takari:maven:wrapper“.

Installation

Auch wenn eine Installation nicht notwendig ist, ist dies natürlich jederzeit möglich. Das Tool kann einfach als ZIP File heruntergeladen werden. Eine komplexe Installation ist nicht notwendig. Es reicht, das ZIP an einer beliebigen Stelle zu entpacken.

Dateien und Verzeichnisse

pom.xml

Das Projekt wird in einer pom.xml (POM = Project Object Model) beschrieben, welches sich in dem Projektverzeichnis befindet.

src

Das src Verzeichnis enthält alle Sourcen der Projekts. Diese sind in der Regel aufgeteilt in mehrere Unterordner: src/main für das eigentliche Projekt und src/test für die automatisierten Tests, die nicht mit ausgeliefert werden. Java Quellcode kommt in ein weiteres Unterverzeichnis java und die Ressourcen in das weitere Unterverzeichnis resources.

target

Alle Dateien, die erzeugt werden, werden in dem Verzeichnis target erzeugt. Je nach Art der erzeugten Datei, werden diese in einem entsprechenden Unterverzeichnis angelegt, z.B. target/classes für die class Dateien.

mvnw / mvnw.cmd / .mvn

Scripte und Verzeichnis des Wrappers. Das Skript mvnw kann statt mvn bei Maven Aufrufen verwendet werden. Die Dateien des Wrappers incl. Konfiguration finden sich in .mvn/wrapper.

Beispiel einer pom.xml

03 helloword – maven/pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>de.kneitzel</groupId>
  <artifactId>hellofx</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>hellofx</name>
  <url>https://blog.kneitzel.de</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>    
	<maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.openjfx</groupId>
      <artifactId>javafx-controls</artifactId>
      <version>11.0.2</version>
    </dependency>
    <dependency>
      <groupId>org.openjfx</groupId>
      <artifactId>javafx-fxml</artifactId>
      <version>11.0.2</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-maven-plugin</artifactId>
        <version>0.0.3</version>
        <configuration>
          <mainClass>helloworld.HelloWorld</mainClass>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Diese Datei kann am Anfang immer so übernommen werden. Folgende Werte sind dabei anzupassen:

groupId / artifactId / version / name / url

Die ersten drei Werte bilden die id des Projekts, welches eindeutig sein sollte. Die übrigen sollten aber auch angepasst werden.

maven.compiler.source / maven.compiler.target

Dies kennzeichnet die Java Version, die wir nutzen wollen. In dem Beispiel ist dies Java 11.

dependencies

Die Abhängigkeiten sind hier einzutragen. Die vorhandenen Libraries auf maven central kann man sich auf https://mvnrepository.com/ ansehen.

build/plugins

Für JavaFX Applikationen wird das javafx-maven-plugin benötigt. Dieses muss eingebunden werden. Weiterhin wird die Klasse mit der main Methode angegeben, damit das plugin die Applikation auch starten kann.

Ausprobieren

Wir können maven einfach direkt mit dem Beispielprojekt ausprobieren. Dazu wechseln wir in das Verzeichnis „03 helloworld – maven“ und geben da einen der folgenden Befehle ein:

  • mvnw clean
    Dies bereinigt das Projekt.
  • mvnw package
    Dies übersetzt das Projekt und baut ein jar File. Dies kann in dem Verzeichnis Target in den diversen Verzeichnissen etwas nachvollzogen werden.
  • mvnw javafx:run
    Dies baut das Projekt und führt die Anwendung danach aus.

Gradle

Übersicht

Gradle ist etwas jünger und entstand 2007. Es hat diverse Dinge von Maven übernommen wie z.B. die Verzeichnisstruktur und das automatische Laden von Abhängigkeiten aus Maven Repositories.

So wie Maven auch, gibt es weitgehende Standards, so dass die Projekt-Konfiguration sehr klein ausfallen kann. Für ein Java Projekt könnte eine Zeile „apply plugin: ‚java’“ ausreichend sein.

Statt einer XML Datei hat Gradle eine eigene Domänenspezifische Sprache für die Konfiguration.

Wrapper

So wie bei Maven existiert auch für Gradle ein Wrapper.

Erzeugung des Wrappers
Es ist möglich, mittels gradle ein Wrapper für ein Projekt zu erzeugen. Dazu geht man in das Verzeichnis des Projekts und ruft auf: „gradle wrapper“.

Installation

Auch wenn eine Installation nicht notwendig ist, ist dies natürlich jederzeit möglich. Das Tool kann einfach als ZIP File heruntergeladen werden. Eine komplexe Installation ist nicht notwendig. Es reicht, das ZIP an einer beliebigen Stelle zu entpacken.

Dateien und Verzeichnisse

build.gradle / settings.gradle / gradle.properties

Das Projekt wird in einer build.gradle beschrieben, welches sich in dem Projektverzeichnis befindet. Die anderen Dateien sind optional.

src

Das src Verzeichnis enthält alle Sourcen der Projekts. Diese sind in der Regel aufgeteilt in mehrere Unterordner: src/main für das eigentliche Projekt und src/test für die automatisierten Tests, die nicht mit ausgeliefert werden. Java Quellcode kommt in ein weiteres Unterverzeichnis java und die Ressourcen in das weitere Unterverzeichnis resources.

gradlew / gradlew.bat / gradle

Scripte und Verzeichnis des Wrappers. Das Skript gradlew kann statt gradle bei Gradle Aufrufen verwendet werden. Die Dateien des Wrappers incl. Konfiguration finden sich in gradle/wrapper.

Beispiel einer build.gradle

plugins {
    id 'application'
    id 'org.openjfx.javafxplugin' version '0.0.8'
}

javafx {
    version = "11.0.2"
    modules = [ 'javafx.controls', 'javafx.fxml' ]
}

group 'de.kneitzel'
version '1.0-SNAPSHOT'

mainClassName = 'helloworld.HelloWorld'

repositories {
    mavenCentral()
}

dependencies {
    compile group: 'org.openjfx', name: 'javafx-controls', version: '11.0.2'
    compile group: 'org.openjfx', name: 'javafx-fxml', version: '11.0.2'
    testCompile group: 'junit', name: 'junit', version: '4.12'
}

Ähnlich wie in Maven findet sich die id der Projekts (group/version – name ist in settings.gradle) und es wird ein javafx Plugin eingebunden.

Die Klasse mit der main Methode wird benannt und die Dependencies aufgelistet.

Zusätzlich ist es aber noch notwendig, die javafx Version sowie die verwendeten Module anzugeben.

Links

Änderungsnachweis

  • 2020-11-02 Überarbeitung Layout
  • 2020-10-30 Erste Veröffentlichung

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.

WSL: Services automatisch starten

Beiträge der WSL Serie

Wir haben im letzten Blog ssh installiert und den Service gestartet.

Wenn wir aber Windows neu gestartet oder WSL mit „wsl -t debian“ beendet haben, dann mussten wir feststellen, dass der ssh Service nicht mehr lief.

WSL startet kein Linux System, wie wir es von vollwertigen Installationen gewohnt sind. Dies kann man auch gut erkennen, wenn wir einmal systemctl aufrufen:

konrad@ZBook15G2:~$ systemctl
System has not been booted with systemd as init system (PID 1). Can't operate.
Failed to connect to bus: Host is down
konrad@ZBook15G2:~$

Dies ist sehr ärgerlich, denn davon sind alle Dienste betroffen. Aber wir wollen ggf. ja cron Einträge machen oder oder per ssh zugreifen. Daher müssen wir dafür sorgen, dass die Dienste, die wir brauchen, gestartet werden.

Schritt 1: sudo ohne Passwort aufrufen

Damit wir generell services manipulieren können, ohne dass ein Passwort eingegeben werden muss, tragen wir dies bei sudoers ein:

echo "%sudo   ALL=(ALL) NOPASSWD: /usr/sbin/service *" > /etc/sudoers.d/service

Danach können wir nun den service Befehl mittels sudo aufrufen ohne dass wir unser Passwort eingeben müssen.

Schritt 2: Prüfen, ob ein Service läuft

Nun können wir prüfen, ob ein Service läuft. Den Status von ssh können wir mittels

service ssh status

anzeigen lassen.

In einem Script können wir den Status auswerten und bei Bedarf den Service neu starten:

[ "`service ssh status | sed 's/.*not running.*/nok/'`" == "nok" ] && sudo service ssh start

Damit wird in der Ausgabe nach „not running“ gesucht um aus dem ganzen Text ein „nok“ zu machen. So das erfolgreich war, wird der Service neu gestartet.

Schritt 3: Script, das alle Services startet, die wir brauchen

In dem Homelaufwerk von meinem User habe ich ein Script services.sh erstellt:

#!/bin/bash
# Start Services if not running

[ "`service ssh status | sed 's/.*not running.*/nok/'`" == "nok" ] && sudo service ssh start
[ "`service dbus status | sed 's/.*not running.*/nok/'`" == "nok" ] && sudo service dbus start
[ "`service cron status | sed 's/.*not running.*/nok/'`" == "nok" ] && sudo service cron start

Schritt 4: Automatisches Starten des Scripts

Damit sicher gestellt ist, dass die Services laufen, wenn ich mit dem System arbeite, habe ich:

Autostart

Im Autostart ein Shortcut erstellt, welcher aufruf:

wsl.exe bash ~/services.sh

Dadurch werden alle Services in meinem Windows Subsystem für Linux gestartet, wenn ich mich anmelde.

.bashrc

In meiner .bashrc habe ich den Aufruf ~/services.sh aufgenommen. Somit wird bei jedem Öffnen einer Shell noch einmal geprüft, ob die Services alle laufen.