0

JDK 11, JavaFX 15.

Despite the wonderful answer How do I determine the correct path for FXML files, CSS files, Images, and other resources needed by my JavaFX Application? this is still stymieing me. It does not address loading related resources.

I have a simple example loading an FXML file that refers to a CSS file.

<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" 
            styleClass="mainFxmlClass" xmlns:fx="http://javafx.com/fxml/1" 
            xmlns="http://javafx.com/javafx/11.0.1">
    <stylesheets>
        <URL value="@/styles/root.css" />
    </stylesheets>
</AnchorPane>

And this is the pkg/App.java class:

package pkg;

public class App extends Application {

    private static Scene scene;

    @Override
    public void start(Stage stage) throws IOException {
        FXMLLoader loader = new FXMLLoader(App.class.getResource("root.fxml"));       
        scene = new Scene(loader.load());
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }
}

This is the root.css file:

.mainFxmlClass {

}

Here is the jar layout (eliminating maven boiler plate, module info, manifest).

pkg/App.class
pkg/root.fxml
pkg/styles/root.css
styles/root.css

The relevant exception I get is:

Caused by: javafx.fxml.LoadException: Invalid resource: /styles/root.css not found on the classpath

According to Introduction to FXML, under Location Resolution, it says:

As strings, XML attributes cannot natively represent typed location information such as a URL. However, it is often necessary to specify such locations in markup; for example, the source of an image resource. The location resolution operator (represented by an "@" prefix to the attribute value) is used to specify that an attribute value should be treated as a location relative to the current file rather than a simple string.

The exception specifies "/styles/root.css", which is an absolute path. As seen from the JAR layout, I have a /styles/root.css. But the documentation says "relative to the current file".

Assuming the "current file" is /pkg/root.fxml, then /pkg/styles/root.css is (should be) relative to /pkg/root.fxml (or is it /pkg/App.class?), yet it can not locate this one either.

If I comment out the stylesheets element, the file loads just fine.

So, where should I place the root.css file?

Will Hartung
  • 115,893
  • 19
  • 128
  • 203
  • Since the error message says `/styles/root.css` cannot be found my guess is that the leading slash causes the problem. What if you use `"@styles/root.css"` instead? – Slaw Feb 13 '21 at 11:56
  • Try `@../styles/root.css` and for the same folder use the answer of Slaw. – micpog90 Feb 13 '21 at 13:18
  • Did you verify the contents of the jar file (eg using `jar -tf` from the command line)? Or are you just showing what you believe is there from your source layout? – James_D Feb 13 '21 at 15:35
  • @James_D It's the actual jar file (minus the stuff I consider not relevant). – Will Hartung Feb 13 '21 at 18:02

1 Answers1

0

To reference a CSS file in a FXML file you can either use:

<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" 
            styleClass="mainFxmlClass" xmlns:fx="http://javafx.com/fxml/1" 
            xmlns="http://javafx.com/javafx/11.0.1">
    <stylesheets>
        <String fx:value="/styles/root.css" />
    </stylesheets>
</AnchorPane>

Note:

  1. String not URL
  2. fx:value not value
  3. without '@' at the beginning
  4. Seems only to support absolute paths

or

<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" 
            styleClass="mainFxmlClass" xmlns:fx="http://javafx.com/fxml/1" 
            xmlns="http://javafx.com/javafx/11.0.1" 
            stylesheets="@/styles/root.css" />
</AnchorPane>

Note:

  1. attribute not nested element
  2. comma separated list of stylesheets
  3. stylesheet URLs must start with '@' here
  4. supports absolute and relative URLs

The next thing is to configure the correct location, which already should be set by the constructor you're using and thus should be fine.

You can find a full sample here.

The sample uses the convention over configuration approach supported by the FXMLLoaders utility class of Drombler Commons.

With your sample this could look something like the following if you rename root.fxml to App.fxml:

    scene = new Scene(FXMLLoaders.load(App.class));

It will also look for a App.properties file (and locale specific derivations ; must be in the same package) which could be useful if you have i18n text in your FXML file. Additionally it will set the correct ClassLoader and location and throws RuntimeExceptions rather than checked exceptions since the user cannot change anything anyway.

You can use this utility class by adding the following dependency:

<dependency>
  <groupId>org.drombler.commons</groupId>
  <artifactId>drombler-commons-fx-core</artifactId>
  <version>1.0</version>
</dependency>

The library is Open Source.

Puce
  • 37,247
  • 13
  • 80
  • 152