Im fünften und letzten Teil dieser Serie befassen wir uns mit JavaFX. JavaFX ist mehr oder weniger der inoffizielle Nachfolger von Swing. Während es sich bei Swing lediglich um ein API für Java handelt, ist JavaFX weit mehr als das, es ist ein Framework mit vielen unterschiedlichen Features.

Zu nennen sind unter anderem, das Java API mit denen Applikationen komplett in Java oder auch anderen JVM-Sprachen, wie Groovy, JRuby oder Scala entwickelt werden können, die XML-basierende Auszeichnungssprache FXML mit dieser Benutzeroberflächen beschrieben werden, die Gestaltung von Oberflächen mittels CSS oder der WYSIWYG-Editor Scene Builder.

JavaFX wird im XBMC Desktop Client als GUI-Framework verwendet. Als Beispiel möchte ich den Login-Bildschirm des Clients aufgreifen und einzeln darstellen, wie dieser in JavaFX gestaltet wird. Vorweg eine kleine Warnung, ich bin kein Designer! Jemand mit Erfahrung könnte den ein oder anderen Augenkrebs von den nachfolgenden Bildern bekommen. ;-)

FXML

Oberflächen können in JavaFX in der XML-basierenden Auszeichnungssprache FXML beschrieben werden. Mit dem oben erwähnten Scene Builder habe ich mir eine simple Login-Oberfläche zusammengeklickt, die im Standard Layout nun wie folgt aussieht:

XBMC Desktop Client - Loginmaske

Fürs Erste reicht das natürlich, vor allem weil das Standard Layout recht ansehnlich ist. Wenn JavaFX allerdings schon die Möglichkeit bietet, die Oberflächen mittels CSS zu gestalten, wollen wir das natürlich auch ausnutzen. Unsere CSS-Datei heißt einfach login.css. Der von Scene Builder generierte FXML-Quellcode inklusive der referenzierten CSS-Datei sieht wie folgt aus:

<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.net.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.*?>
<?import javafx.scene.text.Font?>
<AnchorPane id="root" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="182.0" prefWidth="400.0" style="" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="de.witi.xbmc.jfx.LoginController">
  <children>
    <TextField fx:id="urlField" layoutX="14.0" layoutY="69.0" prefWidth="125.0" promptText="openelec:9090" style="" text="openelec:9090" />
    <TextField editable="false" layoutX="261.0" layoutY="69.0" prefWidth="125.0" text="username" />
    <PasswordField editable="false" layoutX="261.0" layoutY="91.0" prefWidth="125.0" text="password" />
    <Button fx:id="button" defaultButton="true" layoutX="136.0" layoutY="145.0" mnemonicParsing="false" onAction="#connect" prefWidth="125.0" text="Connect" />
    <Label id="header" alignment="CENTER" layoutX="0.0" layoutY="14.0" prefWidth="400.0" text="XBMC Desktop Client" textAlignment="LEFT" textFill="BLACK" textOverrun="CLIP">
      <font>
        <Font name="System Bold" size="20.0" />
      </font>
    </Label>
    <Label fx:id="msgLabel" alignment="CENTER" contentDisplay="CENTER" layoutX="81.0" layoutY="113.0" prefHeight="34.0" prefWidth="238.0" text="Label" textAlignment="CENTER" textFill="BLACK">
      <font>
        <Font size="16.0" />
      </font>
    </Label>
  </children>
  <stylesheets>
    <URL value="@login.css" />
  </stylesheets>
</AnchorPane>

Der XML-Code ist selbstsprechend und sollte keiner weiteren Erklärung benötigen. Hinweisen möchte ich allerdings auf folgende Definition: fx:controller="de.witi.xbmc.jfx.LoginController". Hierbei handelt es sich um die Definition eines sogenannten Controllers. Dieser ist für die Interaktion mit der Oberfläche zuständig ist. Das heißt zum Beispiel, dass wenn ein Benutzer auf den Login-Button klickt, dort die Aktion verarbeitet wird. Interessant sind in diesem Zusammenhang die Attribute fx:id in den einzelnen Elementen sowie folgender Ausdruck im Button onAction="#connect". Auf diese komme ich im weiteren Verlauf des Artikel noch konkret zu sprechen.

CSS

Die CSS-Propertys von JavaFX sind leider nicht die selben, die man bei der HTML-Gestaltung gewohnt ist, kommen denen allerdings recht nah. Da man JavaFX-Applikationen auch in HTML einbetten kann, inklusive entsprechender CSS-Dateien, verwenden alle CSS-Propertys - um nicht mit den regulären CSS- Ausdrücken zu konkurrieren - den Prefix -fx-. Wie zum Beispiel -fx-background-color oder -fx-font-size. Kommen wir nun direkt zu der CSS-Datei. Da ich persönlich auf dunkle Design stehe, wollen wir das Login-Fenster per CSS entsprechend dunkel gestalten. Im Folgenden seht ihr nun den CSS-Code sowie das veränderte Aussehen der GUI:

#root {
  -fx-background-color: linear-gradient(#2F2F2F, #2C2C2C);
  -fx-font-family: sans-serif;
}
#header {
  -fx-alignment: center;
  -fx-text-fill: white;
  -fx-effect: dropshadow(gaussian, rgba(199,199,199,0.9), 15, 0, 0, 5);
  -fx-font-size: 30px;
  -fx-font-weight: bold;
}
.label {
  -fx-text-fill: #fff;
}
.text-input {
  -fx-background-color: #222;
  -fx-text-fill: #fff;
}
.text-input:hover, .text-input:focused {
  -fx-background-color: #444;
}
.button {
  -fx-background-color: #fff;
  -fx-text-fill: #000;
}
.button:hover, .button:focused {
  -fx-background-color: #bbb;
}

XBMC Desktop Client - Loginmaske mit CSS

So, das sieht doch schon mal ganz anders aus. Damit können wir nun leben! :-)

JavaFX Controller

Kommen wir nun zum spannenden Teil: Das Ausführen und Verarbeiten von Aktionen. Glücklicherweise haben wir in der Loginmaske lediglich eine Aktion - den Login - so dass wir nicht viel berücksichtigen müssen. Ohne lange um den heißen Brei herumzureden, komme ich direkt zum bereits erwähnten Controller, der in Scala implementiert wie folgt aussieht:

package de.witi.xbmc.jfx
import javafx.fxml.FXML
import javafx.scene.control.{Button, Label, TextField}
import javafx.event.ActionEvent
import javafx.stage.Stage
class LoginController {
  @FXML
  var urlField: TextField = _
  @FXML
  var msgLabel: Label = _
  @FXML
  var button: Button = _
  def connect(event: ActionEvent) {
    button.setDisable(true)
    /** ... **/
  }
}

War es das etwa? Ja, das war es! Denn das JavaFX Framework übernimmt sehr viel für uns. Die Klasse LoginController wird automatisch instanziiert. Mit Hilfe der Annotation @FXML werden die Formularelemente automatisch (Dependecy Injection) injiziert. Die Methode connect wird automatisch beim Klicken auf den Login-Button in unserer Oberfläche ausgeführt. Alles automatisch! Naja...genau genommen nicht wirklich. Sämtliche benötigen Informationen haben wir JavaFX bereits in der FXML-Definition mitgeteilt:

  • class LoginController -> <AnchorPane ... fx:controller="de.witi.xbmc.jfx.LoginController"
  • @FXML var urlField: TextField = _ -> <TextField fx:id="urlField"
  • def connect -> <Button ... onAction="#connect"

Starten der JavaFX Applikation

Das Einzige was nun noch fehlt, ist die Initialisierung unserer Applikation von der Main-Methode aus. Diese ist im folgenden Codebeispiel zu sehen.

import javafx.application.Application
import javafx.fxml.FXMLLoader
import javafx.scene.{Parent, Scene}
import javafx.stage.Stage
object App {
  def main(args: Array[String]): Unit = {
    Application.launch(classOf[App], args: _*)
  }
}
class App extends Application {
  override def start(primaryStage: Stage) {
    val loader = new FXMLLoader(classOf[App].getResource("/login.fxml"))
    val root = loader.load().asInstanceOf[Parent]
    primaryStage.setTitle("XBMC Desktop Client 0.0.1")
    primaryStage.setScene(new Scene(root))
    primaryStage.show()
  }
}

In der Einstiegsmethode - der main-Methode - wird die statische Methode launch der Klasse Application aufgerufen, die als Parameter unter anderem den Klassennamen einer Oberklasse von Application erwartet. In unserem Fall ist dies App. Beim Starten des Programmes bewirkt dies, dass automatisch die Methode start der Klasse App vom JavaFX Framework aufgerufen und schließlich auch unser Login-Screen angezeigt wird.

Quellcode

Wie von den vorherigen Artikel bereits gewohnt, ist dieser Quellcode wieder unter github zu finden: simple-javafx-gui

blogroll
tags