javamavenjavafxjavafx-css

Can't use a CSS located inside a JAR file in an JavaFX FXML scene


When I open the following FXML file in IntelliJ:

<VBox alignment="CENTER" spacing="20.0" stylesheets="@/org/kordamp/bootstrapfx/bootstrapfx.css" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.demo2.HelloController">
    <padding>
        <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
    </padding>
   
    <Label fx:id="welcomeText" />
    <Button onAction="#onHelloButtonClick" styleClass="alert-danger" text="Hello!" />
</VBox>

I get this error:

enter image description here

Note that in this project I have the BootstrapFX library defined as a dependency in Maven.

I'm assuming that stylesheets="@/org/kordamp/bootstrapfx/bootstrapfx.css" should look into the proper JAR dependency in my Maven external libraries and get the CSS file from there...

enter image description here

Of course, when I run the application from IntelliJ, it yields this error (the css is not found on the classpath):

Caused by: javafx.fxml.LoadException: Invalid resource: /org/kordamp/bootstrapfx/bootstrapfx.css not found on the classpath

So how can I specify in the stylesheets attribute of the FXML (or in my project's configuration) a path to a CSS file located inside a JAR file?

(I know that I can extract the css from the JAR file, copy it in my resources/ folder and include it in the FXML file specifying the relative path to my external css, and it works like a charm, but I would like to know how to do it directly from the JAR file)


Solution

  • Given it appears BootstrapFX is modularized, my guess is that the stylesheet is encapsulated. That means code from outside the library's module cannot access the resource, because the library does not declare the necessary opens directives. Though this should only take effect if BootstrapFX is being loaded as a named module (i.e., on the module-path). Otherwise, I would expect this to work:

    <VBox stylehsheets="org/kordamp/bootstrapfx/bootstrapfx.css"/>
    

    Note: In FXML, the @ prefix is the "location resolution operator". It means to treat the subsequent path as being relative to the current FXML file. I doubt the stylesheet is located at org/kordamp/bootstrapfx/bootstrapfx.css relative to your FXML file.

    That said, it looks like the intended approach is to use BootstrapFX#bootstrapFXStylesheet() in code. That method returns a string URL pointing to the stylesheet. The URL mechanism "bypasses" the encapsulation issue. That means you should consider adding the stylehsheet in the controller. For example:

    import javafx.fxml.FXML;
    import javafx.scene.layout.VBox;
    import org.kordamp.bootstrapfx.BootstrapFX;
    
    public class Controller {
    
        @FXML private VBox theVBox;
    
        @FXML
        private void initialize() {
             theVBox.getStylesheets().add(BootstrapFX.bootstrapFXStylesheet());
        }
    }
    

    However, it is possible to call the bootstrapFXStylesheet() method via FXML using fx:factory. For example:

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import javafx.scene.layout.VBox?>
    <?import org.kordamp.bootstrapfx.BootstrapFX?>
    
    <VBox xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml">
        <stylesheets>
            <BootstrapFX fx:factory="bootstrapFXStylesheet"/>
        </stylesheets>
    </VBox>