Skip to content

Getting Started HelloWorld (deutsch)

Mike Klimek edited this page Oct 3, 2017 · 13 revisions

In dieser Anleitung wird eine einfache HalloWelt-Anwendung mit mvvmFX entwickelt. Benötigt werden:

  • Maven
  • Oracle JDK version 8 oder höher (OpenJDK funktioniert nicht, da dort JavaFX aktuell nicht enthalten ist)
  • Beliebiger Java-Editor/IDE
  • (Optional) JavaFX SceneBuilder

1. Maven-Projekt anlegen

In diesem Tutorial benutzen wir Maven um unseren Build-Prozess zu steuern und unsere Abhängigkeiten aufzulösen. Als erstes legen wir eine Datei POM.xml in unserem Projektverzeichnis an. In der POM.xml wird die Abhängigkeit auf mvvmFX eingetragen sowie die Java-Version bestimmt.

<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/xsd/maven-4.0.0.xsd">
    
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>mvvmfx-helloworld</artifactId>
    <version>1.0.0</version>
    <name>HelloWorld Example</name>
    
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>de.saxsys</groupId>
            <artifactId>mvvmfx</artifactId>
            <version>1.5.2</version>
        </dependency>
    </dependencies>
</project>

Neben der POM-Datei sollten wir auch die für Maven typische Verzeichnis-Struktur anlegen:

project_directory
   +--src
   |   +--main
   |       +--java
   |       +--resources
   +--POM.xml 

2. JavaFX Starter-Klasse

Als erstes legen wir eine Java-Klasse zum Starten der Anwendung an. Im Beispiel benutzen wir das Package com.example.helloworld. Die Klasse nennen wir Starter und fügen eine main-Methode hinzu. Ausserdem leiten wir von javafx.application.Application ab um den Einstiegspunkt in die JavaFX-Anwendung zu definieren.

public class Starter extends Application{
    public static void main(String...args){
        Application.launch(args);
    }

    @Override
    public void start(Stage stage){
        stage.setTitle("Hello World Application");
        stage.show();
    }
}

Diese einfache Anwendung lässt sich bereits starten. Es erscheint ein leeres Fenster mit der Titel-Leiste "Hello World Application".

3. UI anlegen

Um eine richtige Programmoberfläche zu entwickeln gibt es bei JavaFX im Prinzip zwei Varianten: Wir können klassisch im Java-Code die Oberflächen-Elemente instanziieren und zusammenbauen. Alternativ und in vielen Fällen besser geeignet ist jedoch die zweite Variante: Wir beschreiben unsere Oberfläche mit dem XML-Dialekt FXML. Einer der Gründe für diese Variante ist, dass hier das hervorragende Tool JavaFX SceneBuilder zur Verfügung steht um die Oberfläche einfach zusammen zu klicken. Das Tool ist Open-Source und kann hier herunter geladen werden.

MvvmFX unterstützt sowohl Views auf Basis von FXML also auch nur mit Java geschriebene. In diesem Tutorial beschränken wir uns jedoch auf die FXML-Variante.

Bei FXML-Basierten Ansichten besteht die konzeptionelle "View" aus 2 Dateien:

  1. Die FXML-Datei ansich enthält die Struktur und das Layout der Oberfläche. Sie definiert, welche UI-Elemente verwendet werden.
  2. Eine so genannte "CodeBehind" Klasse oder "View Klasse". Dabei handelt es sich um eine Java classe, die Referenzen auf die UI-Controls der FXML-Datei enthält. Sie stellt also die Verbindung von der FXML-Datei zum Java-Code dar.

Beide Dateien zusammen bilden "die View". MvvmFX benutzt einen Convention-Over-Configuration-Ansatz mit einigen Namenskonventionen. Diese besagen, dass sowohl die FXML-Datei und die CodeBehind-Klasse den gleichen Namen (ohne Dateiendung) besitzen sollen und sich im gleichen Verzeichnis/Package befinden müssen.

ViewModel

Bevor wir unsere View-Dateien anlegen, wollen wir zunächst eine ViewModel-Klasse anlegen. Diese nenenn wir HelloWorldViewModel und implementieren das Interface de.saxsys.mvvmfx.ViewModel. In dieser Klasse können wir später View-Spezifische Logik implementieren, fürs Erste bleibt die Klasse jedoch leer.

public class HelloWorldViewModel implements ViewModel {
}

View

Anschließend legen wir die View-Klasse HelloWorldView an. Diese Klasse implementiert das Interface de.saxsys.mvvmfx.FxmlView. Als generischen Typ müssen wir auf das ViewModel verweisen. Damit ist die Logische Verbindung der View zu dem ViewModel hergestellt. Ausserdem implementieren wir das Interface Initializable, welches zu JavaFX gehört. Dieses Interface schreibt eine Methode initialize vor, die automatisch ausgeführt wird, sobald die View komplett geladen ist. Diese Methode werden wir später noch brauchen.

public class HelloWorldView implements FxmlView<HelloWorldViewModel>, Initializable {
    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
    }
}

FXML

Nun legen wir unsere FXML-Datei an. Wie oben bereits erwähnt müssen dafür bestimmte Konventionen eingehalten werden, damit mvvmFX die Datei findet und laden kann. Als Dateinamen verwenden wir HelloWorldView.fxml und legen sie ins gleiche Package wie die View-Datei.

Hinweis für Maven: Maven sieht getrennte Verzeichnisse für Java-Klassen (src/main/java) und andere Resourcen (src/main/resources) vor. Für Maven gelten FXML-Dateien als eben solche anderen Resourcen und sollten deshalb korrekter Weise auch nicht im src/main/java-Verzeichnis sondern unterhalb des src/main/resources-Verzeichnisses abgelegt werden. Wichtig ist jedoch, dass trotzdem die Konvention eingehalten wird und die FXML-Dateien im gleichen Package liegen, d.h. man muss unterhalb von src/main/resources die Package-Struktur mit Verzeichnissen nachbilden. In unserem Beispiel legen wir die FXML-Dateien deshalb in src/main/resources/com/example/helloworld/ ab da die View-Klasse den vollqualifizierten Namen com.example.helloworld.HelloWorldView trägt. Maven fügt beim Bauen letztlich beide Verzeichnisse wieder zusammen und sorgt damit dafür, dass die FXML-Dateien und die Java-Klassen wieder direkt nebeneinander liegen.

Alternativ können wir folgende Maven-Konfiguration in der POM.xml Datei ergänzen. Dadurch werden auch FXML-Dateien herangezogen, die unterhalb von src/main/java direkt in den Java-Packages liegen:

<project ...>
...
<build>
 <resources>
   <resource>
     <directory>src/main/java</directory>
     <includes>
       <include>**/*.fxml</include>
     </includes>
   </resource>
 </resources>
 ...
</build>
...
</project>

Die FXML-Datei könnte man nun z.B. mit dem SceneBuilder gestalten oder aber per Hand mit einem XML/Text-Editor. Zunächst wollen wir lediglich ein Label mit dem Text "HelloWorld" platzieren. Diesen Text legen wir in eine VBox, die wir mit einem padding ausstatten. Wichtig ist hier auch, dass per fx:controller-Attribut eine Referenz auf die View-Klasse angelegt wird.

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

<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<VBox xmlns:fx="http://javafx.com/fxml" alignment="CENTER"
	fx:controller="com.example.helloworld.HelloWorldView">
  <children>
    <Label text="Hello World" />
  </children>
  <padding>
    <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
  </padding>
</VBox>

4. Starter anpassen und mvvmFX-View laden

Die View ist fürs erste fertig. Damit sie jedoch auch angezeigt wird, müssen wir sie zunächst per mvvmFX laden und in den SceneGraph legen. Dafür passen wir die Starter-Klasse an. MvvmFX stellt zum Laden von Views eine Fluent-API bereit: Den FluentViewLoader. Mit diesem können wir auf einfache Weise unsere View laden.

Als Ergebnis bekommen wir ein ViewTuple, welches neben dem ViewModel und dem CodeBehind auch das Root-Element aus der geladenen FXML-Datei enthält. Dieses Root-Element können wir nun in den Scene-Graph der Stage hinein legen und somit anzeigen.

@Override
public void start(Stage stage){
    stage.setTitle("Hello World Application");

    ViewTuple<HelloWorldView, HelloWorldViewModel> viewTuple = FluentViewLoader.fxmlView(HelloWorldView.class).load();

    Parent root = viewTuple.getView();
    stage.setScene(new Scene(root));
    stage.show();
}

5. Anzeigetext in ViewModel verschieben

Bisher wird die angezeigte Meldung in der FXML-Datei hartkodiert. Das Pattern Model-View-ViewModel besagt aber, dass der Zustand der View im ViewModel abgebildet wird. Deshalb wollen wir dies nun anpassen und die angezeigte Meldung stattdessen im ViewModel platzieren.

Im ViewModel legen wir dafür eine StringProperty mit namen "helloMessage" und dem initialen Wert "Hello World" an, inklusive getter und setter.

public class HelloWorldViewModel implements ViewModel {

    private StringProperty helloMessage = new SimpleStringProperty("Hello World");

    public StringProperty helloMessageProperty(){
        return helloMessage;
    }

    public String getHelloMessage(){
        return helloMessage.get();
    }

    public void setHelloMessage(String message){
        helloMessage.set(message);
    }
}

Die Verbindung zwischen dem Label in der FXML-Datei und dem StringProperty im ViewModel wird in der View-Klasse angelegt. Dazu müssen wir aber zunächst in der FXML-Datei dem Label eine fx:id vergeben.

Die Label-Definition in der HelloWorldView.fxml sieht nun folgendermaßen aus:

<Label fx:id="helloLabel"/>

Beachte: Man kann bei FXML für Elemente sowohl das Attribut "id" als auch "fx:id" vergeben. Die "id" ohne das "fx"-Namespace-Präfix wird im Wesentlichen für das Styling per CSS benutzt. Für unsere Zwecke brauchen wir jedoch das Attribut "fx:id". Wenn man möchte, kann man problemlos beide Attribute parallel verwenden und ihnen auch jeweils den gleichen Wert geben um nicht durcheinander zu kommen.

In der View-Klasse können wir nun auf dieses Label zugreifen. Dazu legen wir in der HelloWorldView-Klasse eine Instanzvariable vom Typ Label an und vergeben die Bezeichnung "helloLabel". Der Name ist wichtig an dieser Stelle: Damit die Zuordnung funktioniert muss die Instanzvariable genauso heißen, wie die fx:id, die dem Label in der FXML-Datei gegeben wurde. Zusätzlich müssen wir die Instanzvariable mit der Annotation javafx.fxml.FXML versehen.

Die initialize Methode in der View-Klasse, die wir implementiert aber vorhin noch leer gelassen haben, wird nun benutzt um die Verbindung des Labels mit dem ViewModel festzulegen. Dafür müssen wir uns jedoch erstmal das ViewModel holen. Dazu legen wir eine Instanzvariable vom Typ des ViewModels an, in unserem Fall also HelloWorldViewModel. Diese Instanzvariable versehen wir mit der mvvmFX-Annotation de.saxsys.mvvmfx.InjectViewModel, wodurch sich das Framework um die Zuordnung des ViewModels kümmert:

public class HelloWorldView implements FxmlView<HelloWorldViewModel>, Initializable  {

    @FXML
    private Label helloLabel;

    @InjectViewModel
    private HelloWorldViewModel viewModel;

    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        helloLabel.textProperty().bind(viewModel.helloMessageProperty());
    }
}

Mittels DataBinding wird die Verbindung zwischen dem Label und dem ViewModel hergestellt: Die Zeile helloLabel.textProperty().bind(viewModel.helloMessageProperty()); bedeutet, dass wir die Text-Property des Labels an das "helloMessage"-StringProperty aus dem ViewModel binden. Da im ViewModel als initialer Wert dieses Properties der String "Hello World" vergeben wurde, übernimmt nun das Label diesen Wert und zeigt ihn in der View an.

Der komplette Code des Beispiels ist auch zu finden unter: helloworld

Ausserdem existiert eine Variante dieses Beispiels, in dem die UI nicht mit FXML sondern klassisch mit Java-Code beschrieben wird: helloworld-without-fxml