JavaFX permite la personalización de los elementos graficos de una aplicación Java usando CSS. En este artículo se va a describir cómo se pueden usar archivos de hojas de estilo CSS, aunque también es posible aplicar los estilos directamente desde el código fuente a cada elemento de pantalla.

Lista de estilos de la escena

La escena (Scene) que se utilice en una aplicación JavaFX ofrece la posibilidad de asignarle una serie de hojas de estilos a través de una lista. A dicha lista se le podría añadir una única hoja de estilos, varias o dejarla vacía.

Para acceder a dicha lista de hojas de estilos se puede utilizar el método getStylesheets() de la clase Scene:

scene.getStylesheets()

Para añadir una hoja de estilos a la lista se puede usar el método add() como es habitual en las listas de Java, indicando en este caso el nombre del archivo que contiene los estilos:

scene.getStylesheets().add("nombreHojaEstilos.css")

Hay que tener en cuenta que si no se indica ninguna ruta junto al nombre del archivo, éste debe colocarse en la carpeta src del proyecto.

Hoja de estilos dentro de paquetes

Lo habitual y recomendable es tener los archivos mejor organizados, por lo que conviene alojar los archivos CSS en un paquete propio. Por ejemplo, se puede crear un paquete (o carpeta, es lo mismo) llamada resources dentro de los paquetes de fuentes, con idea de alojar diversos recursos de la aplicación como imágenes, sonidos, o los estilos. Incluso dentro de ese paquete se pueden organizar los recursos por categorías, por lo que se puede crear dentro de él otro paquete que se llame, por ejemplo, css donde se alojarán las hojas de estilo. Observa que en el árbol del proyecto aparece como un único paquete llamado resources.css, aunque realmente se trata de una carpeta dentro de otra. Se vería como algo así en :

Captura 42841

Mientras que en la pestaña Files de NetBeans se vería la estructura real en carpetas:

Captura e111f

Así que para que la aplicación cargue una hoja de estilos ubicada en un paquete dentro de los paquetes de fuentes, debes indicar el nombre del archivo precedido de la ruta de acceso:

scene.getStylesheets().add("resources/css/estilo1.css");

Hoja de estilos en paquete dentro del paquete principal

Otra posible manera de organizar este tipo de archivos (y otros tipos de archivos de recursos) es alojándolos en paquetes dentro del paquete de la clase principal del proyecto. Por ejemplo creando los paquetes resources y css así:

Captura 89088

Aunque aparece como un paquete independiente, realmente es una carpeta dentro de la que contiene la clase Main.java. Este tipo de estructura de paquetes se visualiza mejor si cambias el modo de vista de los paquetes Java como árbol reducido. Observa que ahora aparece como un paquete dentro de otro:

Captura f64a6

Y visto desde la pestaña Files o desde un Explorador de archivos se vería como:

Captura dca4b

En estos casos, en vez de indicar la ruta completa del archivo, conviene indicar la ruta relativa desde la carpeta donde se encuentre la clase Java que va a hacer referencia al archivo CSS. Esto debe hacerse utilizando el método getClass().getResource().toExternalForm() de la siguiente manera:

scene.getStylesheets().add(getClass().getResource("resources/css/estilo1.css").toExternalForm());

Vaciar la lista de estilos

La escena de la aplicación puede tener varias hojas de estilos asociadas si se van añadiendo a la lista más de una hoja.

Si en un momento determinado deseas vaciar completamente la lista de hojas de estilos (para dejar de nuevo los estilos predefinidos) puedes utilizar el método clear() que se encuentra disponible como en otros tipos de listas en Java.

scene.getStylesheets().clear();

Contenido de la hoja de estilos

Selectores

Las clases de estilos que son habituales en CSS se corresponden en JavaFX con el nombre de las clases utilizadas en el código fuente Java. Se deben indicar el nombre de la clase, en minúsculas, precedido por un punto (.), y has de tener en cuenta que si el nombre de la clase está compuesto por varias palabras se deben separar con un guión (-). Por ejemplo:

.button
.check-box
.scroll-bar

También se pueden definir estilos personalizados para elementos concretos asignándoles un identificador. Los identificadores deben indicarse precedidos del carácter almohadilla (#). Por ejemplo:

#boton1

Para asignarle un identificador a un elemento (nodo) de JavaFX se debe usar el método setId(), especificando como parámetro de tipo String el nombre del identificador que se le quiere asignar. Por ejemplo:

Button btnEstilo1 = new Button();
btnEstilo1.setText("Estilo 1");
btnEstilo1.setId("boton1");

También permite el uso de pseudo-clases de la misma manera que se usan en otros lenguajes que soportan CSS, es decir, usando como separador dos puntos (:) y el nombre del estado. Por ejemplo, para cambiar el estilo del botón cuando se posiciona el ratón sobre él:

.button:hover

Reglas y propiedades

Las reglas de una definición de estilo asignan valores a propiedades. Los identificadores de las propiedades en JavaFX están precedidos por -fx-. Debes usar el separador dos puntos (:) entre la propiedad y el valor que se le asigna y se finaliza cada regla con punto y coma (;).

Todo el conjunto de reglas que desees aplicara un selector se deben encerrar entre llaves { }.

En el siguiente ejemplo se asigna un tamaño de fuente de 14px a todos los botones, y sólo al botón que tenga como identificador "boton1" se le aplica color rojo al texto:

.button { 
    -fx-font-size: 14px; 
}
#boton1 { 
    -fx-text-fill: red; 
}

En el artículo JavaFX CSS Reference Guide puedes encontrar todas las propiedades y los posibles valores que se le puede asignar a cada propiedad.

Proyecto de ejemplo

El siguiente código corresponde a una aplicación con 3 botones que permiten modificar la apariencia de los mismos dependiendo del botón que se pulse. Es decir, como si se cambiar el "tema" de la aplicación.

Captura 2ad03 Captura f5c3c Captura 7e9a6

Main.java

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class Main extends Application {
    
    @Override
    public void start(Stage primaryStage) {
        StackPane root = new StackPane();
        Scene scene = new Scene(root, 300, 250);

        Button btnEstilo1 = new Button();
        btnEstilo1.setText("Estilo 1");
        // Asignar un identificador para la hoja de estilos
        btnEstilo1.setId("boton1"); 
        btnEstilo1.setOnAction(new EventHandler() {            
            @Override
            public void handle(ActionEvent event) {
                // Limpiar los estilos que tuviera anteriormente
                scene.getStylesheets().clear();
                // Aplicar la hoja de estilos
                scene.getStylesheets().add(
                    getClass().getResource("resources/css/estilo1.css").toExternalForm());                 
            }
        });
        
        Button btnEstilo2 = new Button();
        btnEstilo2.setText("Estilo 2");
        // Asignar un identificador para la hoja de estilos
        btnEstilo2.setId("boton2"); 
        btnEstilo2.setOnAction(new EventHandler() {            
            @Override
            public void handle(ActionEvent event) {
                // Limpiar los estilos que tuviera anteriormente
                scene.getStylesheets().clear();
                // Aplicar la hoja de estilos
                scene.getStylesheets().add(
                    getClass().getResource("resources/css/estilo2.css").toExternalForm());                 
            }
        });
        
        Button btnEstilo3 = new Button();
        btnEstilo3.setText("Sin estilo");
        // Asignar un identificador para la hoja de estilos
        btnEstilo3.setId("boton3"); 
        btnEstilo3.setOnAction(new EventHandler() {            
            @Override
            public void handle(ActionEvent event) {
                // Limpiar los estilos que tuviera anteriormente
                scene.getStylesheets().clear();
            }
        });
        
        // Organizar los elementos en la ventana
        HBox panelBotones = new HBox();
        panelBotones.setSpacing(20);
        panelBotones.setAlignment(Pos.CENTER);
        root.getChildren().add(panelBotones);
        panelBotones.getChildren().add(btnEstilo1);
        panelBotones.getChildren().add(btnEstilo2);
        panelBotones.getChildren().add(btnEstilo3);
        
        primaryStage.setTitle("Ejemplo de uso de CSS en JavaFX");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

}

estilo1.css

/* Estilo para todos los botones de la aplicación */
.button { 
    -fx-font-size: 14px; 
}
/* Estilo cuando se posiciona el ratón sobre cualquier botón */
.button:hover {
    -fx-font-size: 16px; 
}
/* Estilo para botón con ID boton1 */
#boton1 { 
    -fx-text-fill: red; 
}
/* Estilo para botón con ID boton2 */
#boton2 { 
    -fx-text-fill: orange; 
}
/* Estilo para botón con ID boton3 */
#boton3 { 
    -fx-text-fill: brown; 
}

estilo2.css

/* Estilo para todos los botones de la aplicación */
.button { 
    -fx-font-size: 10px; 
}
/* Estilo para botón con ID boton1 */
#boton1 { 
    -fx-text-fill: blue; 
}
/* Estilo para botón con ID boton2 */
#boton2 { 
    -fx-text-fill: blueviolet; 
}
/* Estilo para botón con ID boton3 */
#boton3 { 
    -fx-text-fill: darkcyan; 
}

Más información