Skip to main content
Entwickler Themen
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

Ein erstes JavaFX-Projekt

Dieses Beispiel erstellt eine vollständige Maven-Anwendung mit einem Namensfeld und einem Label. Änderungen am Feld erscheinen sofort im Label und im JavaBean-Model. Ein Controller ist dafür noch nicht erforderlich.

Bibliothek vorbereiten

Ein JDK 21 installieren. Für Maven steht der Wrapper des Repositorys oder eine Installation ab Version 3.8.6 zur Wahl. Anschließend java -version und ./mvnw -version prüfen. Unter macOS lässt sich das JDK beispielsweise mit export JAVA_HOME=$(/usr/libexec/java_home -v 21) wählen.

git clone https://gitea.neitzel.de/konrad/NeitzelLib.git
cd NeitzelLib
./mvnw -pl fx-example -am install
./mvnw -f fx-example/pom.xml javafx:run

install baut die benötigten Reactor-Module und installiert Parent-POM, core, injection und fx im lokalen Maven-Repository. Eine Veröffentlichung dieser Snapshots in Maven Central wird nicht vorausgesetzt. javafx:run startet anschließend nur das Beispiel. Das Ziel nicht zusammen mit -am auf dem Reactor ausführen: Der Parent ist keine JavaFX-Anwendung.

Testergebnis kontrollieren

Der Parent-POM setzt testFailureIgnore=true. Auch -Dmaven.test.failure.ignore=false allein überschreibt diese feste Konfiguration nicht zuverlässig. Deshalb sind die XML-Berichte maßgeblich. Nach dem Build lässt sich mit Python 3 prüfen:

python3 - <<'PYTHON'
from pathlib import Path
import xml.etree.ElementTree as ET
reports = list(Path('.').glob('*/target/surefire-reports/TEST-*.xml'))
assert reports, 'No test reports found'
failures = []
for report in reports:
    suite = ET.parse(report).getroot()
    if int(suite.get('failures', 0)) or int(suite.get('errors', 0)):
        failures.append(str(report))
assert not failures, 'Failed test reports: ' + ', '.join(failures)
print(f'{len(reports)} test suites passed')
PYTHON

Für die Abnahme einer Änderung zunächst einen sauberen Build ausführen, etwa ./mvnw -pl fx-example -am clean verify. Alte Berichte dürfen keine ausgebliebenen Tests ersetzen. JavaFX-Tests benötigen eine nutzbare grafische Sitzung.

Dateien des eigenen Projekts

Außerhalb des Bibliotheks-Checkouts ein Verzeichnis hello-fx anlegen. Dieses Beispiel verwendet den Classpath und enthält absichtlich keine module-info.java.

hello-fx/
  pom.xml
  src/main/java/example/Main.java
  src/main/java/example/HelloApp.java
  src/main/java/example/Person.java
  src/main/resources/example/Hello.fxml

pom.xml

Der POM benötigt nur fx, ein Logging-Backend und die Plugins für Compiler und Entwicklungsstart:

<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>example</groupId>
  <artifactId>hello-fx</artifactId>
  <version>1.0-SNAPSHOT</version>
  <properties>
    <maven.compiler.release>21</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <dependencies>
    <dependency>
      <groupId>de.neitzel.lib</groupId>
      <artifactId>fx</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-simple</artifactId>
      <version>2.0.17</version>
      <scope>runtime</scope>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.14.1</version>
      </plugin>
      <plugin>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-maven-plugin</artifactId>
        <version>0.0.8</version>
        <configuration><mainClass>example.Main</mainClass></configuration>
      </plugin>
    </plugins>
  </build>
</project>

Person.java

src/main/java/example/Person.java enthält eine gewöhnliche JavaBean:

package example;

public final class Person {
    private String name = "Ada";

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}

Main.java

Der einfache Launcher erweitert selbst nicht Application:

package example;

import javafx.application.Application;

public final class Main {
    public static void main(String[] args) {
        Application.launch(HelloApp.class, args);
    }
}

HelloApp.java

src/main/java/example/HelloApp.java erzeugt Model und Kontext und lädt die View auf dem JavaFX Application Thread:

package example;

import de.neitzel.fx.view.LoadedView;
import de.neitzel.fx.view.ViewLoader;
import de.neitzel.injection.ApplicationContext;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;

public final class HelloApp extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        Person person = new Person();
        ApplicationContext context = new ApplicationContext("example");
        LoadedView view = ViewLoader.load(
                HelloApp.class.getResource("Hello.fxml"), person, context);
        stage.setScene(new Scene(view.getRoot()));
        stage.setTitle("Hello NeitzelLib");
        stage.show();
    }
}

Hello.fxml

Die Ressource unter src/main/resources/example/Hello.fxml ablegen:

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import de.neitzel.fx.view.BindingList?>
<?import de.neitzel.fx.view.DirectBinding?>
<VBox xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1"
      spacing="12" prefWidth="360" prefHeight="120">
    <fx:define>
        <BindingList>
            <DirectBinding control="name" property="text" path="name" direction="both"/>
            <DirectBinding control="echo" property="text" path="name" direction="view"/>
        </BindingList>
    </fx:define>
    <TextField fx:id="name"/>
    <Label fx:id="echo"/>
</VBox>

Starten und ausprobieren

Im Verzeichnis hello-fx:

mvn clean compile javafx:run

Ohne separate Maven-Installation kann der absolute Pfad zum Bibliotheks-Wrapper mit -f /absoluter/pfad/hello-fx/pom.xml clean compile javafx:run verwendet werden.

Das Feld zeigt zunächst Ada. Beim Tippen folgen Label und Person.setName. ViewLoader hält dazu die JavaFX-Property im ViewModel. Ein späterer direkter Aufruf von person.setName(...) benachrichtigt diese Property dagegen nicht. Eine bestehende View wird auf dem JavaFX-Thread beispielsweise so aktualisiert:

((javafx.beans.property.StringProperty) view.property("name")).set("Grace");

property liefert Property<?>; der Cast benennt hier den bekannten Typ. Generierte typisierte Accessors können solche Casts später vermeiden.

Weiterarbeiten

Für Ereignisse und Computed-Methoden kommt ein injizierter Controller hinzu; wiederverwendbare Formulare werden Components. Die Referenzanwendung zeigt beides sowie Converter, Listen und Nachrichten. Vor mehreren Fenstern und verschachtelten Models sind die Regeln zu Identität und Lebensdauer wichtig.

Der Designer kann später ergänzt werden. Die Anwendung läuft unabhängig davon. Für die Auslieferung müssen JavaFX und seine nativen Plattformbibliotheken vorhanden sein; ein gewöhnliches einzelnes JAR enthält sie nicht automatisch. fx-example zeigt JAR-plus-libs, während der Designer ein eigenes Runtime-Bundle besitzt.