12JavaFX
»Die Reparatur alter Fehler kostet oft mehr als die Anschaffung neuer.«
– Wieslaw Brudzinski (1920–1996)
JavaFX bietet attraktive Fähigkeiten zum Design moderner grafischer Oberflächen. Dieses Kapitel beleuchtet auf der Basis von JavaFX 8 Aspekte wie den Aufbau der Oberflächen über Programmcode oder deklarative XML-Dateien, Charts und weitere Grundlagen.
12.1Das erste Programm mit JavaFX
Das erste JavaFX-Programm soll die Grundsätze von JavaFX demonstrieren. Im Mittelpunkt des Interesses stehen der Startprozess und die Art des Objektaufbaus:
Listing 12.1com/tutego/insel/javafx/HelloJavaFX.java
import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.effect.Reflection;
import javafx.scene.text.*;
import javafx.stage.Stage;
public class HelloJavaFX extends Application {
public static void main( String[] args ) {
launch( args );
}
@Override
public void start( Stage stage ) {
Text t = new Text( "Die Java-Insel grüßt JavaFX" );
t.setX( 10.0f );
t.setY( 50.0f );
t.setFont( Font.font( "Calibri", FontWeight.NORMAL, 30 ) );
t.setEffect( new Reflection() );
Group root = new Group( t );
Scene scene = new Scene( root, 400, 100 );
stage.setScene( scene );
stage.setTitle( "JavaFX Demo" );
stage.show();
}
}
Abbildung 12.1Erstes Pixelquicky mit JavaFX
Eine JavaFX-Anwendung ist eine javafx.application.Application
JavaFX-Anwendungen erweitern die Basisklasse Application. Sie vererbt der eigenen Klasse Lebenszyklusmethoden wie init(), destroy(), start(…) oder stop(). Die Methoden werden je nach Wunsch überschrieben – wir überschreiben nur start(Stage).
Die eigene statische main(String[])-Methode leitet an die statische launch(String[])-Methode der Application-Klasse weiter und übergibt ihr alle Kommandozeilenoptionen. Da die Klassenmethode launch(…) weiß, in welcher Klasse sie aufgerufen wurde, erzeugt sie ein Exemplar dieser Klasse und ruft dann die Lebenszyklusmethoden auf. Soll launch(…) von der eigenen Klasse entkoppelt werden, lässt sich eine überladene Methode einsetzen, die über ein Class-Objekt eine andere zu startende JavaFX-Klasse bestimmt.
Start der Anwendung
JavaFX ruft im Lebenszyklus der JavaFX-Anwendung nach dem init() die Methode start(Stage primaryStage) auf. Unsere Klasse überschreibt start(…), um Programmcode abzuarbeiten und die grafische Ausgabe vorzubereiten. JavaFX übergibt der Methode eine Stage (zu Deutsch Bühne), was der Aufgabe eines Haupt-Containers zukommt und am ehesten mit dem Startfenster verglichen werden kann. Diesem Stage-Objekt kann wie bei einem Fenster ein Titel zugewiesen werden, auch die Ausmaße und Dekorationen, oder er kann in den Vollbildmodus gesetzt werden. Am wichtigsten ist aber die Zuweisung einer Szene, die auf den eigentlichen Inhalt verweist. Um eine Analogie zu gebrauchen: Die Szene ist die Leinwand und die Stage der Bilderrahmen mit Leinwand, also die gesamte Bühne. Während die primäre Stage von JavaFX kommt, sind wir es, die den Szenegraphen aufbauen, mit Elementen füllen und dann der Stage zuweisen.
[»]Hinweis
Während ein AWT- oder Swing-Programm selbst ein Fensterobjekt aufbaut, ist die Herangehensweise bei JavaFX fundamental anders. JavaFX baut das Fenster auf und übermittelt es einmalig in der start(Stage)-Methode. Dieses Stage müssen wir uns gut merken, denn alle Oberflächenelemente kommen später auf diesen Container. Stage ist bei Swing mit JFrame vergleichbar.
Der Szenegraph
Zentraler Dreh- und Angelpunkt bei JavaFX ist eine besondere Datenstruktur, genannt Szenegraph (engl. scene graph). Er nimmt grafische Objekte wie Linien, Flächen, Textfelder, Browser, 3D-Objekte auf und verwaltet sie. Zu jedem Zeitpunkt sind also alle grafischen Objekte einer JavaFX-Anwendung im Speicher präsent, und sie existieren nicht nur zum Zeitpunkt des Zeichnens. (Für sehr große Datenmengen lassen sich andere Lösungen finden, etwa dass die Grafiken gezeichnet werden und diese dann in den Szenegraphen gehängt werden.)
JavaFX repräsentiert über die Klasse Scene den Szenegraphen, der dann mit stage.setScene(scene) auf die Bühne kommt. Jetzt müssen nur noch die Objekte auf die Scene.
Auf die Bühne
Ein Szenegraph ist eine hierarchische Datenstruktur, die ein Wurzelelement haben muss. Daher wird bei Aufbau eines Scene-Objekts im Konstruktor ein Wurzelobjekt übergeben, das ist entweder ein einzelnes GUI-Element oder ein Container. Üblicherweise weisen wir Scene einen Knoten-Container vom Typ Group zu. Eine Group ist selbst ein Knoten, sodass sich Gruppen auch schachteln lassen – es ist das typische Composite-Pattern.
Group ist der Behälter für Knoten, die intern in einer beobachtbaren Datenstruktur gehalten werden. Diese Datenstruktur muss mit getChildren() erfragt werden – das Ergebnis ist eine ObservableList<Node> –, und daran können wir unsere Objekte hängen – ein direktes add(…) hat Group nicht. Group kann auch gleich im Vararg-Konstruktor alle Knoten annehmen.
Knoten
Alle Objekte im Szenegraphen sind vom Typ Node. Ein Untertyp von Node ist javafx.scene. shape.Shape, was Elemente wie Line, Text oder javafx.scene.control.Control repräsentiert, wozu Texteingabefelder oder Schaltflächen zählen.
Text-Objekte repräsentieren Beschriftungen, die über den Konstruktor oder über setContent(String) gesetzt werden können. Als Zeilentrenner wird »\n« unterstützt. Wir nutzen einige Setter im Beispiel:
setX(float)/setY(float) setzen die Koordinaten.
setFill(Paint) setzt eine Farbe oder Gradienten – bei Farben sind es javafx.scene.paint. Color-Objekte.
setFont(Font) setzt einen neuen Zeichensatz.
setEffect(…) setzt in unserem Fall einen Spiegeleffekt. Fast 20 Effekte bringt JavaFX mit, darunter GaussianBlur, Glow, SepiaTone oder Shadow.
Alle Knoten wie unser Textelement müssen dem Szenegraphen hinzugefügt werden, wozu wir sie der Group hinzufügen.
JavaFX versus Swing/AWT
Die Java2D-API brachte gegenüber dem AWT die Neuerung, dass es erstmalig grafische Objekte vom Typ java.awt.Shape gab. Sie können in einer Datenstruktur gesammelt werden und repräsentieren die Eigenschaften wie Koordinaten in einem Objekt. Linien mussten also nicht mehr direkt über drawLine() gezeichnet werden, sondern konnten als java.awt.geom.Line2D.Double-Objekte aufgebaut und verwaltet werden. JavaFX geht noch einen Schritt weiter und zeigt uns gar keinen Grafikkontext mehr, sondern gibt uns nur die Möglichkeit, Objekte zu sammeln und in den Graphen zu hängen. Dabei nutzt JavaFX auch Shape-Typen, aber nicht eine Schnittstelle java.awt.Shape mit Implementierungen im Paket java.awt.geom, sondern ganz eigene Typen – JavaFX hat mit den java.awt-Typen überhaupt nichts gemeinsam. So nutzt JavaFX auch eigene Typen für Farben und Fonts, etwa javafx.scene.paint.Color statt java.awt.Color.