Styliser JavaFX avec CSS

JavaFX intègre un puissant support CSS qui permet de personnaliser l'apparence des composants de l'interface utilisateur. Cette fonctionnalité permet de séparer le style visuel de la structure et du comportement de l'application.

Introduction au CSS dans JavaFX

Le CSS (Cascading Style Sheets) dans JavaFX est similaire au CSS utilisé pour le web, mais avec quelques différences importantes :

Avantages de l'utilisation du CSS dans JavaFX

Emplacement des fichiers CSS

Dans un projet Maven/Gradle standard, les fichiers CSS sont généralement placés dans le dossier des ressources :

src/
├── main/
│   ├── java/
│   │   └── com/
│   │       └── example/
│   │           └── javafxdemo/
│   │               └── MainApplication.java
│   └── resources/
│       └── com/
│           └── example/
│               └── javafxdemo/
│                   ├── styles/
│                   │   ├── application.css     // Style principal
│                   │   ├── dark-theme.css      // Thème sombre
│                   │   └── controls.css        // Styles pour les contrôles
│                   └── views/
│                       └── main-view.fxml

Application des styles CSS

Il existe plusieurs façons d'appliquer des styles CSS à une application JavaFX :

1. Via le code Java

// Application à toute la scène
Scene scene = new Scene(root, 800, 600);
scene.getStylesheets().add(getClass().getResource("/com/example/javafxdemo/styles/application.css").toExternalForm());

// Application à un nœud spécifique
Button button = new Button("Cliquez-moi");
button.getStylesheets().add(getClass().getResource("/com/example/javafxdemo/styles/button-styles.css").toExternalForm());

// Styles inline (à éviter pour les applications complexes)
Label title = new Label("Titre principal");
title.setStyle("-fx-font-size: 24px; -fx-font-weight: bold; -fx-text-fill: #3366cc;");

2. Via FXML

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

<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>

<VBox xmlns:fx="http://javafx.com/fxml" fx:controller="com.example.javafxdemo.MainController">
    <stylesheets>
        <URL value="@../styles/application.css" />
        <URL value="@../styles/controls.css" />
    </stylesheets>
    
    <children>
        <Label text="Titre" styleClass="title-label" />
        <Button text="Style par défaut" />
        <Button text="Style personnalisé" styleClass="custom-button" />
        <Button text="Style inline" style="-fx-background-color: #ff6666;" />
    </children>
</VBox>

Sélecteurs CSS dans JavaFX

JavaFX prend en charge la plupart des sélecteurs CSS standards :

Type de sélecteur Syntaxe Exemple
Type de classe .class-name .button - Tous les boutons
ID #id-name #submit-button - Élément avec fx:id="submit-button"
Type d'élément element-type Button - Tous les boutons
Pseudo-classe element:pseudo-class .button:hover - Boutons au survol
Combinaison parent > child .vbox > .button - Boutons enfants directs d'un VBox

Exemple de fichier CSS pour JavaFX :

/* Styles pour tous les boutons */
.button {
    -fx-background-color: #3498db;
    -fx-text-fill: white;
    -fx-font-weight: bold;
    -fx-padding: 8px 16px;
    -fx-cursor: hand;
}

/* État survol */
.button:hover {
    -fx-background-color: #2980b9;
}

/* État pressé */
.button:pressed {
    -fx-background-color: #1f618d;
}

/* Bouton de type danger */
.button.danger-button {
    -fx-background-color: #e74c3c;
}

/* Bouton désactivé */
.button:disabled {
    -fx-background-color: #bdc3c7;
    -fx-opacity: 0.7;
}

/* Sélection par ID */
#main-title {
    -fx-font-size: 24px;
    -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif;
    -fx-font-weight: bold;
}

/* Sélecteur combiné */
.vbox > .label {
    -fx-padding: 5px;
}

Propriétés CSS courantes dans JavaFX

Voici quelques-unes des propriétés CSS les plus utilisées dans JavaFX :

Texte et police

-fx-font-family: "Arial", sans-serif;
-fx-font-size: 14px;
-fx-font-weight: bold; /* normal, bold, 700 */
-fx-font-style: italic; /* normal, italic, oblique */
-fx-text-fill: #333333;
-fx-text-alignment: center; /* left, center, right, justify */
-fx-underline: true;
-fx-strikethrough: true;

Fond et bordures

-fx-background-color: #f5f5f5;
-fx-background-radius: 5px;
-fx-border-color: #cccccc;
-fx-border-width: 1px;
-fx-border-radius: 5px;
-fx-border-style: solid; /* solid, dotted, dashed */

Dimensions et espacement

-fx-pref-width: 200px;
-fx-pref-height: 30px;
-fx-min-width: 100px;
-fx-max-width: 300px;
-fx-padding: 10px; /* haut droite bas gauche: 10px 15px 10px 15px */
-fx-spacing: 10px; /* Pour VBox, HBox */

Effets

-fx-opacity: 0.8;
-fx-cursor: hand;
-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 10, 0, 0, 3);

Exemple complet : Style d'un formulaire

Voici un exemple complet d'un formulaire JavaFX stylisé avec CSS :

FXML (form-view.fxml)

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

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

<GridPane xmlns:fx="http://javafx.com/fxml" fx:controller="com.example.javafxdemo.FormController"
          styleClass="form-container" hgap="10" vgap="10">
    <padding>
        <Insets top="20" right="20" bottom="20" left="20"/>
    </padding>
    
    <stylesheets>
        <URL value="@../styles/form-style.css"/>
    </stylesheets>
    
    <Label text="Formulaire d'inscription" styleClass="form-title" 
           GridPane.columnIndex="0" GridPane.rowIndex="0" GridPane.columnSpan="2"/>
    
    <Label text="Nom :" GridPane.columnIndex="0" GridPane.rowIndex="1"/>
    <TextField fx:id="nameField" promptText="Entrez votre nom" 
             GridPane.columnIndex="1" GridPane.rowIndex="1"/>
    
    <Label text="Email :" GridPane.columnIndex="0" GridPane.rowIndex="2"/>
    <TextField fx:id="emailField" promptText="Entrez votre email" 
             GridPane.columnIndex="1" GridPane.rowIndex="2"/>
    
    <Label text="Mot de passe :" GridPane.columnIndex="0" GridPane.rowIndex="3"/>
    <PasswordField fx:id="passwordField" promptText="Entrez votre mot de passe" 
                  GridPane.columnIndex="1" GridPane.rowIndex="3"/>
    
    <CheckBox fx:id="agreeCheckbox" text="J'accepte les conditions d'utilisation" 
             GridPane.columnIndex="0" GridPane.rowIndex="4" GridPane.columnSpan="2"/>
    
    <HBox spacing="10" alignment="CENTER_RIGHT" 
         GridPane.columnIndex="0" GridPane.rowIndex="5" GridPane.columnSpan="2">
        <Button text="Annuler" styleClass="cancel-button" onAction="#handleCancel"/>
        <Button text="S'inscrire" styleClass="submit-button" onAction="#handleSubmit"/>
    </HBox>
    
    <Label fx:id="statusLabel" styleClass="status-label" 
           GridPane.columnIndex="0" GridPane.rowIndex="6" GridPane.columnSpan="2"/>
</GridPane>

Ce code va dans src/main/resources/com/example/javafxdemo/views/form-view.fxml

CSS (form-style.css)

/* Styles pour le formulaire */
.form-container {
    -fx-background-color: white;
    -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.1), 10, 0, 0, 5);
    -fx-background-radius: 8px;
}

.form-title {
    -fx-font-size: 20px;
    -fx-font-weight: bold;
    -fx-text-fill: #333333;
    -fx-padding: 0 0 10px 0;
}

/* Styles pour les champs de saisie */
.text-field, .password-field {
    -fx-pref-height: 35px;
    -fx-background-color: #f5f5f5;
    -fx-background-radius: 4px;
    -fx-border-color: #e0e0e0;
    -fx-border-radius: 4px;
    -fx-padding: 0 10px;
    -fx-font-size: 14px;
}

.text-field:focused, .password-field:focused {
    -fx-background-color: white;
    -fx-border-color: #3498db;
    -fx-effect: dropshadow(three-pass-box, rgba(52, 152, 219, 0.2), 5, 0, 0, 0);
}

/* Styles pour les boutons */
.button {
    -fx-background-radius: 4px;
    -fx-padding: 8px 15px;
    -fx-font-weight: bold;
    -fx-cursor: hand;
}

.submit-button {
    -fx-background-color: #2ecc71;
    -fx-text-fill: white;
}

.submit-button:hover {
    -fx-background-color: #27ae60;
}

.submit-button:pressed {
    -fx-background-color: #229954;
}

.cancel-button {
    -fx-background-color: transparent;
    -fx-text-fill: #7f8c8d;
    -fx-border-color: #bdc3c7;
    -fx-border-radius: 4px;
}

.cancel-button:hover {
    -fx-background-color: #ecf0f1;
}

/* Style pour la case à cocher */
.check-box {
    -fx-padding: 10px 0;
}

.check-box .box {
    -fx-background-color: white;
    -fx-border-color: #bdc3c7;
    -fx-border-radius: 3px;
}

.check-box:selected .mark {
    -fx-background-color: white;
}

.check-box:selected .box {
    -fx-background-color: #3498db;
}

/* Style pour l'étiquette de statut */
.status-label {
    -fx-padding: 10px 0 0 0;
    -fx-font-style: italic;
}

.status-success {
    -fx-text-fill: #2ecc71;
}

.status-error {
    -fx-text-fill: #e74c3c;
}

Ce code va dans src/main/resources/com/example/javafxdemo/styles/form-style.css

Contrôleur (FormController.java)

package com.example.javafxdemo;

import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;

public class FormController {
    @FXML private TextField nameField;
    @FXML private TextField emailField;
    @FXML private PasswordField passwordField;
    @FXML private CheckBox agreeCheckbox;
    @FXML private Label statusLabel;
    
    @FXML
    private void handleSubmit() {
        if (!validateForm()) {
            return;
        }
        
        // Traitement du formulaire
        statusLabel.setText("Inscription réussie !");
        statusLabel.getStyleClass().remove("status-error");
        statusLabel.getStyleClass().add("status-success");
    }
    
    @FXML
    private void handleCancel() {
        nameField.clear();
        emailField.clear();
        passwordField.clear();
        agreeCheckbox.setSelected(false);
        statusLabel.setText("");
    }
    
    private boolean validateForm() {
        // Validation simple
        if (nameField.getText().trim().isEmpty()) {
            showError("Le nom est requis");
            return false;
        }
        
        if (emailField.getText().trim().isEmpty() || !emailField.getText().contains("@")) {
            showError("Email invalide");
            return false;
        }
        
        if (passwordField.getText().length() < 6) {
            showError("Le mot de passe doit contenir au moins 6 caractères");
            return false;
        }
        
        if (!agreeCheckbox.isSelected()) {
            showError("Vous devez accepter les conditions d'utilisation");
            return false;
        }
        
        return true;
    }
    
    private void showError(String message) {
        statusLabel.setText(message);
        statusLabel.getStyleClass().remove("status-success");
        statusLabel.getStyleClass().add("status-error");
    }
}

Ce code va dans src/main/java/com/example/javafxdemo/FormController.java

Thèmes clair/sombre

Une application courante du CSS dans JavaFX est la création de thèmes clair et sombre que l'utilisateur peut changer à la volée.

Style commun (common.css)

/* Variables communes */
* {
    -fx-font-family: "Segoe UI", Arial, sans-serif;
}

/* Classes utilitaires */
.container {
    -fx-padding: 20px;
}

/* Styles de base pour les boutons */
.button {
    -fx-padding: 8px 15px;
    -fx-background-radius: 4px;
    -fx-cursor: hand;
}

Ce code va dans src/main/resources/com/example/javafxdemo/styles/common.css

Thème clair (light-theme.css)

/* Thème clair */
.root {
    -fx-base: #ececec;
    -fx-background: #f5f5f5;
    -fx-control-inner-background: white;
    -fx-text-fill: #333333;
    -fx-accent: #3498db;
}

.title {
    -fx-text-fill: #2c3e50;
}

.label {
    -fx-text-fill: #333333;
}

.button {
    -fx-background-color: #3498db;
    -fx-text-fill: white;
}

.button:hover {
    -fx-background-color: #2980b9;
}

.text-field {
    -fx-background-color: white;
    -fx-border-color: #bdc3c7;
}

Ce code va dans src/main/resources/com/example/javafxdemo/styles/light-theme.css

Thème sombre (dark-theme.css)

/* Thème sombre */
.root {
    -fx-base: #2c3e50;
    -fx-background: #1a1a1a;
    -fx-control-inner-background: #2c3e50;
    -fx-text-fill: #ecf0f1;
    -fx-accent: #3498db;
}

.title {
    -fx-text-fill: #ecf0f1;
}

.label {
    -fx-text-fill: #ecf0f1;
}

.button {
    -fx-background-color: #3498db;
    -fx-text-fill: white;
}

.button:hover {
    -fx-background-color: #2980b9;
}

.text-field {
    -fx-background-color: #34495e;
    -fx-border-color: #2c3e50;
    -fx-text-fill: #ecf0f1;
}

Ce code va dans src/main/resources/com/example/javafxdemo/styles/dark-theme.css

Application du thème dans le code Java

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class ThemeApplication extends Application {
    private Scene scene;
    private boolean isDarkTheme = false;
    
    @Override
    public void start(Stage primaryStage) throws Exception {
        // Chargement de la vue principale
        FXMLLoader loader = new FXMLLoader(getClass().getResource("views/main-view.fxml"));
        Parent root = loader.load();
        
        // Création de la scène avec le thème par défaut (clair)
        scene = new Scene(root, 800, 600);
        scene.getStylesheets().add(getClass().getResource("styles/common.css").toExternalForm());
        scene.getStylesheets().add(getClass().getResource("styles/light-theme.css").toExternalForm());
        
        // Bouton pour changer de thème (ajouté directement dans le code pour l'exemple)
        Button themeToggle = new Button("Basculer en mode sombre");
        themeToggle.setOnAction(e -> toggleTheme(themeToggle));
        
        // Ajout du bouton à la vue
        ((VBox) root).getChildren().add(0, themeToggle);
        
        // Configuration et affichage
        primaryStage.setTitle("Application avec thèmes");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    private void toggleTheme(Button themeToggle) {
        // Suppression du thème actuel
        scene.getStylesheets().remove(1);
        
        if (isDarkTheme) {
            // Passage au thème clair
            scene.getStylesheets().add(getClass().getResource("styles/light-theme.css").toExternalForm());
            themeToggle.setText("Basculer en mode sombre");
        } else {
            // Passage au thème sombre
            scene.getStylesheets().add(getClass().getResource("styles/dark-theme.css").toExternalForm());
            themeToggle.setText("Basculer en mode clair");
        }
        
        isDarkTheme = !isDarkTheme;
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

Ce code va dans src/main/java/com/example/javafxdemo/ThemeApplication.java

Bonnes pratiques avec CSS dans JavaFX

Exemple de variables CSS pour une palette cohérente

/* Définition des variables de couleur */
.root {
    /* Couleurs primaires */
    -fx-color-primary: #3498db;
    -fx-color-primary-dark: #2980b9;
    -fx-color-primary-light: #a9cce3;
    
    /* Couleurs secondaires */
    -fx-color-secondary: #2ecc71;
    -fx-color-secondary-dark: #27ae60;
    
    /* Couleurs d'alerte */
    -fx-color-success: #2ecc71;
    -fx-color-warning: #f39c12;
    -fx-color-danger: #e74c3c;
    
    /* Couleurs neutres */
    -fx-color-text: #333333;
    -fx-color-text-light: #7f8c8d;
    -fx-color-background: #f5f5f5;
    -fx-color-border: #bdc3c7;
}

/* Utilisation des variables */
.button.primary {
    -fx-background-color: -fx-color-primary;
    -fx-text-fill: white;
}

.button.primary:hover {
    -fx-background-color: -fx-color-primary-dark;
}

.label.error {
    -fx-text-fill: -fx-color-danger;
}