zurück zum Artikel

Visualisierung in Java mit JavaFX

Gerrit Grunwald

Java auf dem Desktop mag zwar in die Jahre gekommen sein, ist allerdings allen Nörglern zum Trotz noch weit verbreitet. Mit JavaFX steht eine Möglichkeit zur Verfügung, Anwendungen ansprechend zu gestalten und den Staub von mit Swing gebauten Applikationen zu blasen.

Visualisierung in Java mit JavaFX

Auch wenn es nicht mehr "Hype" ist, ist Java auf dem Desktop noch immer weit verbreitet. Als Framework zur Visualisierung kommt überwiegend das etwas in die Jahre gekommene Java Swing zum Einsatz. Mit ihm lassen sich zwar so ziemlich alle Arten von Benutzeroberflächen bewerkstelligen, allerdings gelingt das nicht mit den Bordmitteln, sondern erfordert einen teilweise erheblichen Aufwand.

Deshalb sehen viele Swing nutzende Desktop-Anwendungen nicht besonders schick aus, hat man den Fokus doch häufiger auf die Funktionen als auf das Aussehen gelegt. In der Vergangenheit störte das meist nicht weiter, aber in Zeiten von iOS, Android, OS X, Ubuntu und Windows 8 ist der Durchschnittsanwender bei den Oberflächen verwöhnt, und somit wirken die Swing-Oberflächen teilweise verstaubt.

Mit JavaFX gibt es eine Technik, die es ermöglichen soll, auch in Java ansehnliche Desktop-Anwendungen zu schreiben. JavaFX ist gar nicht mehr so neu, wie es den Anschein hat, seine Geschichte reicht zurück bis ins Jahr 2006, als es unter dem Namen F3 (Form Follows Function) von Chris Oliver entwickelt wurde, um die UI-Entwicklung in Java zu vereinfachen. Mit der Übernahme der Firma Seebeyond durch Sun wurde Oliver mit seiner Entwicklung Teil von Sun Microsystems, und als man dort auf der Suche nach einem Swing-Nachfolger war, kam F3 gelegen. Das Unternehmen entschloss sich, F3 in JavaFX umzubenennen und es zu einem kompletten UI-Framework auszubauen.

Leider hat das Projekt nie richtig abgehoben, was nicht zuletzt an der Art und Weise lag, in der bei Sun entwickelt wurde. So stellte man JavaFX auf Konferenzen in drei aufeinanderfolgenden Jahren jeweils als absolute Neuheit vor. Das Ganze wurde erst besser, als Oracle Sun Microsystems übernahm und man sich dort dazu entschloss, JavaFX konsequent als Nachfolger von Swing zu etablieren. Dazu wurde zunächst die JavaFX eigene Sprache JavaFX Script durch eine Implementierung in "reines" Java ersetzt, um die Einstiegshürde für Java-Programierer so gering wie möglich zu halten. Des Weiteren hielten die Entwickler die aufgestellte Roadmap ein, was zu Sun-Zeiten eher nicht der Fall war.

Im Prinzip gilt folgendes: JavaFX 1.x mit JavaFX Script ist aus der Sun-Ära, und seit JavaFX 2 gibt es eine Java-API. Dabei muss man Oracle zugute halten, dass das Unternehmen versucht hat, die Vorteile von JavaFX Script soweit es geht nach Java zu portieren, was Entwicklern Features wie Properties und Bindings beschert hat.

JavaFX kommt seit JDK 7 Update 6 immer zusammen mit dem JDK, man muss sich somit nicht erst irgendwo ein JAR herunterladen, sondern kann im Prinzip sofort mit JavaFX loslegen. Ein weiterer Vorteil der Sun-Übernahme durch Oracle ist die Tatsache, dass nun die JDK-Versionen für alle Plattformen bei Oracle entstehen. Das führt dazu, dass am selben Tag Versionen für Windows, Mac OS X und Linux veröffentlicht werden, was plattformübergreifende Entwicklung ermöglicht. JavaFX 2.x steht ebenfalls mit all seinen Features auf den genannten Plattformen zur Verfügung. In der nächsten Version können sich Entwickler unter anderem über Unterstützung für ARM-Geräte (wie Raspberry Pi und BeagleBoard xM) freuen. Ein weiteres Novum in der Entwicklung von JavaFX ist die Tatsache, dass der Quellcode komplett als Teil des OpenJDK (Unterprojekt OpenJFX) zur Verfügung stehen wird.

Wenn man sich nun anschaut, was genau JavaFX ist, kommt man schnell zu dem Schluss, dass es sich nur um eine neue Bibliothek handelt, deren API man sich aneignen muss. Es ist keine komplett neue Sprache, sondern einfach Java. Eine gute IDE-Unterstützung für Entwickler ist folglich sichergestellt (zurzeit ist man mit allen drei großen IDEs gut bedient: NetBeans 7.3, E(fx)clipse und IntelliJ IDEA 12.1).

Die Architektur von JavaFX ist ebenfalls interessant, da sie von Grund auf neu ist und nicht auf Swing beziehungsweise dem Abstract Window Toolkit (AWT) basiert. Das bedeutet allerdings auch, dass man nicht ohne weiteres JavaFX und Swing/AWT miteinander mischen kann (aber auch hier gibt es Lösungen, auf die später eingegangen wird). JavaFX besteht aus mehreren Bausteinen, die man in Abbildung 1 dargestellt sieht.

Architektur von JavaFX (Abb. 1)

Architektur von JavaFX (Abb. 1)


Die wichtigsten Teile sind:

Bei Prism handelt es sich um die neue Rendering Engine, die Gebrauch von der Grafikhardware macht (falls unterstützt). Dabei wird auf OS X und Linux mit OpenGL gerendert, während unter Windows Direct 3D zum Einsatz kommt. Wird die Grafikhardware nicht unterstützt (bei Hardware, die nicht älter als drei Jahre ist, sollte das allerdings der Fall sein) wird ein Software-Fallback auf Java2D verwendet. Das Glass Windowing Toolkit stellt den Zugriff auf Low-Level-Betriebssystemroutinen zur Verfügung. Das Quantum Toolkit verknüpft es dann mit Prism und stellt beides der Java-API zur Verfügung.

Wie erwähnt ist JavaFX unabhängig von Java Swing/AWT und arbeitet mit einem sogenannten Scene Graph. Bei diesem, kommt das sogenannte RetainedMode Rendering zum Einsatz. Hierbei kümmert sich ein Framework beziehungsweise eine Library um das Rendering, wohingegen beim sogenannten ImmediateMode Rendering, dass Java Swing/AWT einsetzt, der Entwickler in der Hand hat, welcher Inhalt wo und wann zu rendern ist.

Der JavaFX Scene Graph (Abb. 2)

Der JavaFX Scene Graph (Abb. 2)


Der JavaFX Scene Graph ist ein vorwärtsgerichteter Graph, der sogenannte Nodes umfasst. Dabei unterscheidet man drei verschiedene Typen:

Der Root Node ist der erste Node im Scene Graph und auch der einzige, der keinen Elternknoten hat. Der Branch Node hat die Möglichkeit, andere Nodes aufzunehmen und somit als Elternknoten (Parent) zu operieren. Aus diesem Grund werden zumeist JavaFX-Layoutcontainer (Pane, StackPane, etc.) als Branch Nodes verwendet. Der Leaf Node kann hingegen keine weiteren Nodes aufnehmen und ist meist etwas wie ein grafisches Primitiv (Rechtecke, Kreise, Linien, etc.), ein Steuerelement, ein Bild oder ein Media-Objekt.

Auch der Aufbau einer JavaFX-Applikation spiegelt den Szenengraphen wieder. Als Vergleich und zum besseren Verständnis lässt sich eine Theaterbühne heranziehen. Hier gibt es eine Bühne, auf der sich das Theaterstück abspielt. In JavaFX spricht man deshalb auch von einer Stage (deutsch: Bühne), auf der man seine Applikation anzeigt. In einem Theaterstück gibt es meistens verschiedene Szenen, die auf der Bühne gespielt werden. Im JavaFX Scene Graph gibt es dementsprechend sogenannte Scenes, die sich auf die Stage laden lassen. Man kann sich also zum Beispiel vorstellen, dass man die verschiedenen Views in seiner Software über mehrere Scenes erstellt und sie zum Zeitpunkt des Verwendens auf die Stage lädt. Die jeweilige Scene enthält die entsprechenden Layoutcontainer (Branch Nodes) mit ihrem Inhalt (weitere Branch und Leaf Nodes).

Wichtig ist noch zu wissen, dass eine Stage ein Fenster darstellt und man in einer JavaFX-Applikation zumindest mit einer Stage anfängt. Von dieser initialen Stage kann man weitere Stages (Fenster) öffnen, sollte man sie benötigen. Es folgt das schon obligatorische "Hello World"-Beispiel in JavaFX:

public class HelloWorld extends Application {
private Label label;

@Override public void init() {
label = new Label("Hello World");
}

@Override public void start(Stage stage) {
StackPane root = new StackPane();
root.getChildren().add(label);

Scene scene = new Scene(root, 200, 200);

stage.setTitle("Hello World Example");
stage.setScene(scene);
stage.show();
}

@Override public void stop() {}

public static void main(String[] parameters) {
launch(parameters);
}
}

Das Ergebnis des Programms zeigt Abbildung 3.

Ausgabe des "Hello World"-Programms (Abb. 3)

Ausgabe des "Hello World"-Programms (Abb. 3)

Die Programmstruktur des Beispiels spiegelt den typischen Aufbau einer JavaFX-Applikation wieder. Dabei wird zunächst die Klasse Application erweitert, was erfordert, dass zumindest eine Methode start() vorhanden ist. Des Weiteren gibt es noch drei optionale Methoden init(), stop() und main().

Die main()-Methode ist nötig, um die JavaFX-Applikation startbar zu machen und Applikationsparameter mit zu übergeben. Daraufhin wird automatisch die init()-Methode aufgerufen, in der Entwickler dann zum Beispiel Member-Variablen initialisieren kann. Ist das geschehen, ruft das Programm die start()-Methode auf, der automatisch eine Stage übergeben wird. Dabei handelt es sich um das Anwendungsfenster, in dem das ausführende Gerät die Applikation anzeigt.

Durch Beenden des Programms (zum Beispiel durch den Aufruf von Platform.exit()), ruft man die Methode stop() auf. In ihr besteht die Gelegenheit, verwendete Ressourcen wie Netzverbindungen wieder freizugeben.

Weitere bedeutende Features in JavaFX sind die sogenannten Properties und Bindings. Letztere setzen das Konzept um, die Werte von Variablen an andere Variablen zu binden. Ist der Wert der Variablen A an den Wert der Variablen B gebunden, wird A automatisch den Wert von Variable B erhalten, ohne dass man A explizit setzen muss. Damit das funktioniert, benötigt man die sogenannten Properties, die sich wie folgt verwenden lassen:

// Initialization of the property
private DoubleProperty value = new SimpleDoubleProperty(0);

// The getter method
public double getValue() {
return value.get();
}

// The setter method
public void setValue(double newValue) {
value.set(newValue);
}

// The property method (enables the binding)
public DoubleProperty valueProperty() {
return value;
}

Im Codebeispiel sieht man, dass, anstelle eines primitiven Datentyps vom Typ double, ein neuer Datentyp vom Typ DoubleProperty verwendet wurde. Diese Typen "wrappen" einen primitiven Datentyp (hier double) und stellen zusätzliche Funktion zur Verfügung. Jede Property verfügt zum Beispiel standardmäßig über eine get()- und eine set()-Methode. Die Konvention sieht vor, dass man zum Verwenden von Binding eine Methode zur Verfügung stellen muss, die mit Property endet (hier valueProperty()) und das Property-Objekt zurückliefert. Der folgende Quelltextausschnitt soll das erwähnte Binding mit dieser Methode erklären.

IntegerProperty number1 = new SimpleIntegerProperty(1);
IntegerProperty number2 = new SimpleIntegerProperty(2);
DoubleProperty number3 = new SimpleDoubleProperty(0.5);

// High-Level Binding (Fluent API)
NumberBinding sum1 = number1.add(number2);
NumberBinding result1 = number1.add((number2).multiply(number3));

// High-Level Binding (Binding class)
NumberBinding sum2 = Bindings.add(number1, number2);
NumberBinding result2 = Bindings.add(number1, multiply(number2,number3));

// Low-Level Binding
DoubleBinding db = new DoubleBinding() {
{
super.bind(number1, number2, number3);
}

@Override protected double computeValue() {
return (number1.get() + number2.get() * number3.get());
}
}

Wie sich erkennen lässt, gibt es mehrere Möglichkeiten, das Binding in JavaFX anzuwenden. Man unterscheidet prinzipiell zwischen High-Level- und Low-Level-Binding, wobei sich das High-Level-Binding in die sogenannte Fluent API und das Verwenden der Bindings-Klasse aufteilt. Bindings und Properties einzusetzen erfordert auf Seiten der Entwickler etwas mehr Aufwand durch die zusätzlichen Property-Methoden. Auf der Anwenderseite ermöglichen sie es jedoch, vergleichsweise übersichtlichen Code zu schreiben, da man keine zusätzlichen Listener für Variablen-Änderungen mehr erstellen muss.

In JavaFX gibt es einen Node mit der Möglichkeit, HTML zu rendern – also mit anderen Worten eine Browser Control, genannt WebView. Der WebView Node kapselt dabei eine WebKit Rendering Engine, die den meisten geläufig sein sollte. Sie ist Bestandteil von Chrome, Safari und den Browsern auf den mobilen Platformen Android und iOS. WebKit ermöglicht es, HTML5-Inhalte in einer JavaFX-Anwendung zu verwenden (zum Beispiel Google Maps). Eine Website lässt sich wie folgt in WebView laden:

WebView   browser = new WebView();
WebEngine webkit = browser.getEngine();
webkit.load("http://maps.google.de");

Stackpane root = new StackPane();
root.getChildren().add(browser);

Scene scene = new Scene(root, 300, 300);

stage.setScene(scene);
stage.show();

Die ersten drei Zeilen laden die Seite, der Rest des Listings dient lediglich zur Darstellung des WebViews in der Applikation (Abb. 4).

Beispiel: WebView mit Google Maps (Abb. 4)

Beispiel: WebView mit Google Maps (Abb. 4)

Damit man zeitgemäße Anwendungen erstellen kann, ist es durchaus sinnvoll, hier und da verschiedene Effekte und Animationen in seiner Anwendung einzusetzen (wobei man damit sparsam umgehen sollte). JavaFX bietet hierfür die Möglichkeit, Nodes im Scene Graph zu animieren und mit Effekten wie Schatten (InnerShadow und DropShadow) zu versehen. Möchte man also einem Node im Scene Graph einen Schatten hinzufügen, ist dazu lediglich die Methode setEffect() aufzurufen und als Parameter ein DropShadow-Objekt zu übergeben.

// Create DropShadow Object
DropShadow shadow = new DropShadow();
shadow.setOffsetX(0);
shadow.setOffsetY(2);
shadow.setRadius(5);
shadow.setColor(Color.DARKGRAY);

Button button = new Button("Click Me");
button.setEffect(shadow);
Links ein Button ohne, rechts einer mit Schattenwurf (Abb. 5)

Links ein Button ohne, rechts einer mit Schattenwurf (Abb. 5)

In Abbildung 5 sieht man im Vergleich einen Button ohne (links) und einen mit (rechts) DropShadow.

Mit den Animationen verhält es sich ähnlich einfach wie mit den Effekten. Animationen sind fester Bestandteil der JavaFX API und lassen sich über sogenannte Timeline-Objekte verwenden. Dabei kommt das Konzept sogenannter KeyFrames zum Einsatz, das im folgenden kurz erläutert werden soll.

Zunächst legt man ein Timeline-Objekt an, das man später verwenden möchte. Es ist nicht notwendig für jede Animation eine neue Timeline anzulegen, lediglich das Timeline-Objekt ist mit neuen KeyFrames zu bestücken. Möchte man beispielsweise ein Rechteck von links nach rechts über den Bildschirm bewegen, würde man folgendermaßen vorgehen:

  1. Neues Timeline-Objekt erzeugen.
  2. Neues KeyValue-Objekt für die xProperty des Rechtecks erzeugen und mit Zielwert versehen.
  3. Neues KeyFrame-Objekt erzeugen, den Zeitpunkt und den KeyValue festlegen.
  4. Die Timeline abspielen.

Man erzeugt also ein sogenanntes KeyValue-Objekt für die xProperty des Rechtecks. Bei dem Objekt gibt man nun den Zielwert für die ausgewählte Eigenschaft an (in vorliegenden Fall ließe sich zum Beispiel 300 für die Zielposition von x festlegen) und falls gewünscht zusätzlich noch die zu verwendende Interpolation für die Animation (zum Beispiel linear, ease in, ease both etc.). Mit diesem KeyValue-Objekt legt man nun ein KeyFrame-Objekt an, in dem man angibt, nach welcher Zeit das entsprechende KeyValue-Objekt seinen Wert erreichen soll. Danach fügt man den KeyFrame zum Timeline-Objekt hinzu und kann die Timeline abspielen. Der Code für dieses Beispiel sähe wie folgt aus:

@Override public void start(Stage stage) {
Rectangle rect = new Rectangle(10, 50, 25, 25);

// Create the KeyValue for the xProperty and value of 300px
KeyValue keyValue = new KeyValue(rect.xProperty(), 300,
Interpolator.EASE_BOTH);

// Create the KeyFrame with the KeyValue and the time of 2 sec
KeyFrame keyFrame = new KeyFrame(Duration.millis(2000),
keyValue);

// Create the timeline and add the KeyFrame
Timeline timeline = new Timeline();
timeline.getKeyFrames().add(keyFrame);

// Add the rectangle to a layout container and create a scene
Pane pane = new Pane();
pane.getChildren().add(rect);

Scene scene = new Scene(pane, 335, 125);

stage.setTitle("Animation");
stage.setScene(scene);
stage.show();

// Play the timeline
timeline.play();
}
Beispiel für eine Animation mit AnimationTimer (Abb. 6)

Beispiel für eine Animation mit AnimationTimer (Abb. 6)


Aus Swing-Zeiten ist es bekannt, mithilfe eines javax.swing.Timer-Objekts intervalgesteuerte Aufrufe auf dem Event Dispatch Thread auszuführen. Eine ähnliche Funktion steht auch unter JavaFX mit dem AnimationTimer zur Verfügung, der durch den sogenannten Pulse (getriggert mit 60 FPS) aufgerufen wird.

Da der AnimationTimer aus dem FX Application Thread aufgerufen wird, kann man in seiner Behandlungsroutine eigene Berechnungen für zeitlich gesteuerte Aufrufe erstellen. Die zeitliche Auflösung liegt im Nanosekundenbereich, im Vergleich zu Mikrosekunden beim Swing Timer. Möchte man also beispielsweise alle zwei Sekunden den Zustand einer Shape ändern (zum Beispiel Blinken eines Kreises), würde der benötigte Code folgendermaßen aussehen:

private static final long INTERVAL      = 2_000_000_000l;
private Circle circle = new Circle(50);
private long lastTimerCall = System.nanoTime();
private boolean toggle = false;
private AnimationTimer timer = new AnimationTimer() {
@Override public void handle(long now) {
if(now > lastTimerCall + INTERVAL) {
toggle ^= true;
circle.setVisible(toggle);
lastTimerCall = now;
}
}
};

@Override public void start(Stage stage) {
StackPane pane = new StackPane();
pane.setPadding(new Insets(10, 10, 10, 10));
pane.getChildren().add(circle);

Scene scene = new Scene(pane);

stage.setScene(scene);
stage.show();

// Start the timer
timer.start();
}

Startet man das Programm, blinkt alle zwei Sekunden ein schwarzer Kreis.

Auch zeitlich gesteuerte Animationen sind mit AnimationTimer möglich (Abb. 7).

Auch zeitlich gesteuerte Animationen sind mit AnimationTimer möglich (Abb. 7).

Damit stehen dem Entwickler alle Möglichkeiten zur Verfügung, die man benötigt, um grafische Ausgaben zeitgesteuert zu verändern.

Ein weiteres Novum in JavaFX ist die Verwendung von CSS (Cascading Style Sheets) zur Darstellung von Kontrollelementen oder Ähnlichem. Dabei kommt CSS 2.1 (nach W3C) zum Einsatz. Anhand der in Abbildung 7 gezeigten Animation lässt sich zeigen, wie man beispielsweise den Stil des Kreises mit CSS verändern kann.

Der Kreis soll nun statt einfach schwarz mit einem Farbgradienten gefüllt und mit einem Schlagschatten versehen werden. Das lässt sich mit folgendem Code umsetzen, der in einer einzelnen Datei zu speichern ist (hier demo.css):

.circle {
-fx-fill : radial-gradient(center 50% 25%,
radius 75%,
rgb(122,255,77) 0%,
rgb(0,102,0) 100%);
-fx-effect: dropshadow(gaussian,
rgba(0, 0, 0, 0.65),
10, 0.0, 5, 5);
}

Diesen Style mit dem Selector .circle muss man nun der Kreis-Shape zuweisen. In JavaFX geschieht das durch den Aufruf von getStyleClass().add(".circle"), womit es jedoch noch nicht getan ist, da man der Anwendung noch mitteilen muss, dass sie das Stylesheet laden muss. Das lässt sich durch den Aufruf von

getStyleSheets().add(getClass().getResource("demo.css").toExternalForm())

erreichen.

Das Beispiel von oben sieht demnach wie folgt aus:

private static final long INTERVAL      = 2_000_000_000l;
private Circle circle = new Circle(50);
private long lastTimerCall = System.nanoTime();
private boolean toggle = false;
private AnimationTimer timer = new AnimationTimer() {
@Override public void handle(long now) {
if(now > lastTimerCall + INTERVAL) {
toggle ^= true;
circle.setVisible(toggle);
lastTimerCall = now;
}
}
};

@Override public void start(Stage stage) {
// Add the style class to the circle shape
circle.getStyleClass().add("circle");

StackPane pane = new StackPane();
pane.setPadding(new Insets(10, 10, 10, 10));
pane.getChildren().add(circle);

Scene scene = new Scene(pane);

// Add the stylesheet to the scene
scene.getStylesheets().add(getClass()
.getResource("demo.css").toExternalForm());

stage.setScene(scene);
stage.show();

// Start the timer
timer.start();
}

Führt man den Code aus, erhält man wieder den blinkenden Kreis, diesmal jedoch mit einem radialen Gradienten gefüllt und einem Schatten versehen.

Mit CSS-Styling lässt sich das Aussehen von Objekten anpassen (Abb. 8).

Mit CSS-Styling lässt sich das Aussehen von Objekten anpassen (Abb. 8).


Mit JavaFX ist Java gut gerüstet, um grafisch ansprechende Anwendungen zu gestalten. Der Umfang von JavaFX ist selbstverständlich wesentlich größer, als es sich in diesem Artikel darstellen lässt, weshalb lediglich ein paar Highlights beleuchtet wurden. Allerdings fehlen in der aktuellen Version 2.2 von JavaFX noch wesentliche Bestandteile, die man von einer Benutzeroberfläche erwarten würde. So sind zur Zeit noch keine Dialoge vorhanden, was den Einsatz von JavaFX 2.2 in Business-Applikationen ein wenig erschwert, da man sie sich selbst erstellen muss. Auch ein TreeTable-Control sucht man zur Zeit noch vergebens. Als Ausblick sei jedoch erwähnt, dass diese Dinge in der nächsten Version von JavaFX (JavaFX 8) vorhanden sein werden.

Gerrit Grunwald
arbeitet als Software Engineer bei der Canoo Engineering AG (Schweiz). Dort beschäftigt er sich bevorzugt mit JavaFX, Java Swing und HTML5. Außerdem ist er Mitgründer und Leader der Java Usergroup Münster sowie Community Co-Lead der JavaFX Community.
(jul [1])


URL dieses Artikels:
https://www.heise.de/-1902233

Links in diesem Artikel:
[1] mailto:jul@heise.de