Search code examples
javacssjavafxstylesheet

JavaFX loading a stylesheet


JavaFX has a method that is added to the controller:
public void initialize(URL url, ResourceBundle rb)

This seems to run before any of the controls are added to the scene, because when I add this to it:

@Override
public void initialize(URL url, ResourceBundle rb){
    String treeItemCss = getClass().getResource("/media/css/TreeItem.css").getPath();
    main.getScene().getStylesheets().add(treeItemCss);
}

The CSS:

.tree-cell{
    -fx-indent: 100;
    -fx-underline: true;
}

I get an error from this method: getStylesheets(). But if I move that to an OnAction and execute that action I get no errors.

So my question is, is there a method that runs after all the controls are added to the scene, or a good way to add css to items that are created from a user action, such as a button click?


Solution

  • The initialize() method runs at the end of the FXMLLoader's load() method. Since you don't get a reference to the root of the FXML until that completes, there's obviously no way you can add it to a scene until after then.

    You can:

    Add the css to the Scene in the application code. I.e. Somewhere you create an FXMLLoader, call load(), and add the result to the Scene. Just set the css file on the scene right there, or:

    Add the css stylesheet to the root node instead of to the scene (assuming main is a Parent):

    public void initialize() {
        String treeItemCss = ... ;
        main.getStylesheets().add(treeItemCss);
    }
    

    or:

    Observe the Scene property and add the stylesheet when it changes to something not null:

    public void initialize() {
        String treeItemCss = ... ;
        main.sceneProperty().addListener((obs, oldScene, newScene) -> {
            if (newScene != null) {
                newScene.getStylesheets().add(treeItemCss);
            }
        });
    }
    

    Update Here is a complete example to demonstrate the second option. Everything is in the "application" package:

    Main.java:

    package application;
    
    import javafx.application.Application;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.Scene;
    import javafx.scene.layout.BorderPane;
    import javafx.stage.Stage;
    
    
    public class Main extends Application {
        @Override
        public void start(Stage primaryStage) {
            try {
                BorderPane root = FXMLLoader.load(getClass().getResource("Main.fxml"));
                Scene scene = new Scene(root,400,400);
                primaryStage.setScene(scene);
                primaryStage.show();
            } catch(Exception e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    Main.fxml:

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import javafx.scene.layout.BorderPane?>
    <?import javafx.scene.control.TreeView?>
    <?import javafx.scene.control.TreeItem?>
    
    <BorderPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.MainController" fx:id="root">
        <center>
        <TreeView>
            <root>
                <TreeItem value="Root">
                    <children>
                        <TreeItem value="One"/>
                        <TreeItem value="Two"/>
                        <TreeItem value="Three"/>
                    </children>
                </TreeItem>
            </root>
        </TreeView>
        </center>
    </BorderPane>
    

    MainController.java:

    package application;
    
    import javafx.fxml.FXML;
    import javafx.scene.layout.BorderPane;
    
    public class MainController {
        @FXML
        private BorderPane root ;
    
        public void initialize() {
            root.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
        }
    }
    

    application.css:

    .tree-cell{
        -fx-indent: 100;
        -fx-underline: true;
    }
    

    Note that you can add the stylesheet directly in the FXML file with

    <BorderPane xmlns:fx="..." fx:controller="..." stylesheets="@application.css">
    

    and then omit it completely from the controller logic.