Wie kann man das Layout einer JavaFX-Anwendung mit Cascading Stylesheets formatieren?

Das Layout und die Erscheinung graphischer Komponenten (Nodes) können in JavaFX durch Cascading Stylesheets konfiguriert werden. Syntax und Anwendung folgen dabei weitgehend den CSS-Vorgaben des W3C.

Cascading Style Sheets (CSS) sind ein mächtiges Werkzeug, das im Wesenlichen aus der Gestaltung von HTML-Seiten bekannt ist. Sein Prinzip besteht darin, sog. Selektoren bestimmte Eigenschaften (z.B. Farben, Ränder, Abstände, etc.) zuzuweisen. Auf den generellen Aufbau und allgemeinen Gebrauch der CSS kann an dieser Stelle nicht eingegangen werden. Hierzu sei z.B. auf das CSS-Tutorial der W3 Schools verwiesen.

JavaFX übernimmt die CSS in angepasster Form, wobei sich so weit wie möglich an die W3C-Standards gehalten wird.
Einen vollständigen Überblick über den Aufbau und die Anwendung von CSS in JavaFX bietet der JavaFX CSS Reference Guide. Das in JavaFX verwendete Default-Stylesheet befindet sich unter dem Pfad jfxrt.jar/com/sun/javafx/scene/control/skin/caspian/caspian.css.

CSS-Syntax

CSS-Anweisungen setzen sich aus einer Eigenschaft, einem zugehörigen durch Doppelpunkt (':') getrennten Wert und einem abschließenden Semikolon (';') zusammen. Dabei können mehrere Anweisungen in einem durch geschweifte Klammern gebildeten Block zusammengefasst werden. Das jeweils letzte Semikolon innerhalb eines Blocks kann entfallen.

Selektor-1 [, Selektor-2 [, ...] ] {
    Eigenschaft-1: Wert-1;
    ...
    Eigenschaft-n: Wert-n[;]
}

Die Bezeichner der Eigenschaften entsprechen oft, jedoch keinesfalls immer, denjenigen des Web-CSS. Meist wird jedoch ein '-fx-' vorangestellt (-fx-background-color, -fx-font-family, etc.). Zudem gibt es eine Reihe an Eigenschaften, die speziell für JavaFX geschaffen wurden. Welche Eigenschaften schließlich auf welche Selektoren angewendet werden können erläutert wiederum die JavaFX CSS Reference

Selektoren

Die durch CSS formatierbaren Knoten (Node) des Szenegraphen (scene graph) sind alle von den abstrakten Klassen Region oder Control abgeleitet. Ihre Selektoren können durch die Methode getTypeSelektor() ermittelt werden, sind meist identisch mit den Klassenbezeichnern und entsprechen in ihrer Funktion den Elementtypen im Web-CSS (z.B. table, div, img, etc.).
Darüber hinaus besitzen viele Knoten einen vorvergebenen CSS-Klassenbezeichner und eine String-Instanzvariable zum Speichern einer ID. Die CSS-Klassenbezeichner können in der JavaFX CSS Reference nachgeschlagen werden und entsprechen meist ebenfalls dem JavaFX-Klassennamen. Sie übernehmen jedoch nicht die in Java übliche CamelCase-Schreibweise, sondern verwenden stattdessen die Kleinschreibung mit Bindestrich (RadioButtonradio-button, ScrollBarscroll-bar, etc.). Es muss beachtet werden, dass ein Knoten auch mehreren CSS-Klassen angehören kann. Die ID ist nicht vorbelegt.

RadioButton rb = new RadioButton();
System.out.println(rb.getTypeSelector());  // RadioButton
System.out.println(rb.getStyleClass());    // radio-button
System.out.println(rb.getId());            // null

Zuweisung von Styles

In JavaFX können Styles inline oder als in einer externen Datei deklarierte Stylesheets zugewiesen werden. Ein Inline-Style formatiert nur den angesprochenen Knoten und überschreibt ggf. vorangegangene Styles.
Um einem Knoten einen Inline-Style zuzuweisen, kann die Methode setStyle() der Klasse Node verwendet werden.

Label label = new Label("Text auf Label");
label.setStyle("-fx-background-color: darksalmon; -fx-font-size: 20px");

Zentrale Stylesheets werden in einer gesonderten Datei deklariert. Hierzu wird zunächst eine ObservableList aller registrierten Stylesheets der Scene ermittelt. Ihr kann dann mittels der Methode add() wiederum der URL-String der neuen Stylesheet-Datei beigefügt werden.

Scene scene = new Scene(new Group());
scene.getStylesheets().add(getClass().getResource("/styles/cssTest.css").toExternalForm());

In der Stylesheet-Datei werden die Styles der obigen Syntax gemäß deklariert. Als Selektoren können dabei der Typ-Selektor, eine CSS-Klasse oder eine ID, bzw. Kombinationen aus diesen dienen.

Label {
    -fx-background-color: #f31100;
}

.grid-pane {
    -fx-alignment: center;
    -fx-grid-lines-visible: true;
    -fx-hgap: 15;
    -fx-vgap: 15;
}

#rechts-unten {
    -fx-font-family:'serif';
    -fx-font-size:30;
}

@-Regeln

Ab JavaFX 8 werden einige @-Regeln (At-rules) unterstützt. So können weitere Stylesheets und Schriftdateien in eine Stylesheetdatei eingebunden werden. Hierbei sollten die Vorgaben des W3C hinsichtlich der Reihenfolge eingehalten werden.

@import "cssTest.css";

@font-face {
    font-family: 'orbitron';
    font-weight: bold;
    src: url('/frameworks/javafx/css/orbitron-black.otf');
}

Ein Fenster mit und ohne Formatierung

Das folgende Beipiel zeigt ein einfaches Fenster ohne CSS-Formatierung:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;

public class CSSTest extends Application {
    public static void main(String[] args) {
       launch(CSSTest.class, args);
    }
    
    @Override
    public void start(Stage primaryStage) throws Exception {
        Label lo = new Label("links oben");
        Label ro = new Label("rechts oben");
        Button lu = new Button("links unten");
        RadioButton ru = new RadioButton("rechts unten");
        GridPane gp = new GridPane();
        gp.add(lo, 0, 0);
        gp.add(ro, 1, 0);
        gp.add(lu, 0, 1);
        gp.add(ru, 1, 1);
        Scene scene = new Scene(gp, 400, 150);
        primaryStage.setScene(scene);
        primaryStage.setTitle("JavaFX CSS-Test");
        primaryStage.show();
    }
}
JavaFX-Fenster ohne CSS-Formatierung

Das gleiche Fenster mit CSS-Formatierung:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;

public class CSSTest extends Application {
    public static void main(String[] args) {
       launch(CSSTest.class, args);
    }
    
    @Override
    public void start(Stage primaryStage) throws Exception {
      Label lo = new Label("links oben");
      
      Label ro = new Label("rechts oben");
      ro.setStyle("-fx-background-color: darksalmon; -fx-font-size: 20px;");
      
      Button lu = new Button("links unten");
      lu.setStyle("-fx-text-fill: lightseagreen;");
      
      RadioButton ru = new RadioButton("rechts unten");
      ru.setId("rechts-unten");
      
      GridPane gp = new GridPane();
      gp.getStyleClass().add("grid-pane"); // CSS-Klassenname: grid-pane
      gp.add(lo, 0, 0);
      gp.add(ro, 1, 0);
      gp.add(lu, 0, 1);
      gp.add(ru, 1, 1);
      Scene scene = new Scene(gp, 400, 150);
      scene.getStylesheets().add(getClass().getResource("/styles/cssTest.css").toExternalForm());
      primaryStage.setScene(scene);
      primaryStage.setTitle("JavaFX CSS-Test");
      primaryStage.show();
    }
}
/*************** cssTest.css *****************/

@import "HelloJavaFXWorld.css";

@font-face {
    font-family: 'orbitron';
    font-weight: bold;
    src: url('/frameworks/javafx/css/orbitron-black.otf');
}

.root {
    -hell-oliv: #bcbc8f;
}

GridPane {
    -fx-background-color: -hell-oliv;
}

Label {
    -fx-background-color: #f31100;
}

.grid-pane {
    -fx-alignment: center;
    -fx-grid-lines-visible: true;
    -fx-hgap: 15;
    -fx-vgap: 15;
}

.pane {
    -fx-background-color: #f0f088;
}

#rechts-unten {
    -fx-font-family:'orbitron';
    -fx-font-size:30;
}

/**************** HelloJavaFXWorld.css ******************/

.button {
    -fx-background-color: #f00;
}

.label {
    -fx-background-color: #0f0;
}
JavaFX-Fenster mit CSS-Formatierung
Quellen
  1. JavaFX CSS Reference Guide

Wenn Ihnen javabeginners.de gefällt, freue ich mich über eine Spende an diese gemeinnützigen Organisationen.