Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
402 views
in Technique[技术] by (71.8m points)

How do I determine the correct path for FXML files, CSS files, Images, and other resources needed by my JavaFX Application?

My JavaFX application needs to be able to find the FXML files to load them with the FXMLLoader, as well as stylesheets (CSS files) and images. When I try to load these, I often get errors, or the item I'm trying to load simply doesn't load at runtime.

For FXML files, the error message I see includes

Caused by: java.lang.NullPointerException: location is not set

For images, the stack trace includes

Caused by: java.lang.IllegalArgumentException: Invalid URL: Invalid URL or resource not found

How do I figure out the correct resource path for these resources?

Question&Answers:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Short version of answer:

  • Use getClass().getResource(...) or SomeOtherClass.class.getResource(...) to create a URL to the resource
  • Pass either an absolute path (with a leading /) or a relative path (without a leading /) to the getResource(...) method. The path is the package containing the resource, with . replaced with /.
  • Do not use .. in the resource path. If and when the application is bundled as a jar file, this will not work. If the resource is not in the same package or in a subpackage of the class, use an absolute path.
  • For FXML files, pass the URL directly to the FXMLLoader.
  • For images and stylesheets, call toExternalForm() on the URL to generate the String to pass to the Image or ImageView constructor, or to add to the stylesheets list.
  • To troubleshoot, examine the content of your buid folder (or jar file), not your source folder.

Full Answer

Contents

  1. Scope of this answer
  2. Resources are loaded at runtime
  3. JavaFX uses URLs to load resources
  4. Rules for resource names
  5. Creating a resource URL with getClass().getResource(...)
  6. Organizing code and resources
  7. Maven (and similar) standard layouts
  8. Troubleshooting

Scope of this answer

Note that this answer only addresses loading resources (for example FXML files, images, and stylesheets) that are part of the application, and bundled with it. So, for example, loading images that the user chooses from the file system on the machine on which the application is running would require different techniques that are not covered here.

Resources are loaded at runtime

The first thing to understand about loading resources is that they, of course, are loaded at runtime. Typically, during development, an application is run from the file system: that is, the class files and resources required to run it are individual files on the file system. However, once the application is built, it is usually executed from a jar file. In this case, the resources such as FXML files, stylesheets, and images, are no longer individual files on the filesystem but are entries in the jar file. Therefore:

Code cannot use File, FileInputStream, or file: URLs to load a resource

JavaFX uses URLs to load resources

JavaFX loads FXML, Images, and CSS stylesheets using URLs.

The FXMLLoader explicitly expects a java.net.URL object to be passed to it (either to the static FXMLLoader.load(...) method, to the FXMLLoader constructor, or to the setLocation() method).

Both Image and Scene.getStylesheets().add(...) expect Strings that represent URLs. If URLs are passed without a scheme, they are interpreted relative to the classpath. These strings can be created from a URL in a robust way by calling toExternalForm() on the URL.

The recommended mechanism for creating the correct URL for a resource is to use Class.getResource(...), which is called on an appropriate Class instance. Such a class instance can be obtained by calling getClass() (which gives the class of the current object), or ClassName.class. The Class.getResource(...) method takes a String representing the resource name.

Rules for resource names

  • Resource names are /-separated path names. Each component represents a package or sub-package name component.
  • Resource names are case-sensitive.
  • The individual components in the resource name must be valid Java identifiers

The last point has an important consequence:

. and .. are not valid Java identifiers, so they cannot be used in resource names.

These may actually work when the application is running from the filesystem, though this is really more of an accident of the implementation of getResource(). They will fail when the application is bundled as a jar file.

Similarly, if you are running on an operating system that does not distinguish between filenames that differ only by case, then using the wrong case in a resource name might work while running from the filesystem, but will fail when running from a jar file.

Resource names beginning with a leading / are absolute: in other words they are interpreted relative to the classpath. Resource names without a leading / are interpreted relative to the class on which getResource() was called.

A slight variation on this is to use getClass().getClassLoader().getResource(...). The path supplied to ClassLoader.getResource(...) must not begin with a / and is always absolute, i.e. it is relative to the classpath. It should also be noted that in modular applications, access to resources using ClassLoader.getResource() is, under some circumstances, subject to rules of strong encapsulation, and additionally the package containing the resource must be opened unconditionally. See the documentation for details.

Creating a resource URL with getClass().getResource()

To create a resource URL, use someClass.getResource(...). Usually, someClass represents the class of the current object, and is obtained using getClass(). However, this doesn't have to be the case, as described in the next section.

  • If the resource is in the same package as the current class, or in a subpackage of that class, use a relative path to the resource:

     // FXML file in the same package as the current class:
     URL fxmlURL = getClass().getResource("MyFile.fxml");
     Parent root = FXMLLoader.load(fxmlURL);
    
     // FXML file in a subpackage called `fxml`:
     URL fxmlURL2 = getClass().getResource("fxml/MyFile.fxml");
     Parent root2 = FXMLLoader.load(fxmlURL2);
    
     // Similarly for images:
     URL imageURL = getClass().getResource("myimages/image.png");
     Image image = new Image(imageURL.toExternalForm());
    
  • If the resource is in a package that is not a subpackage of the current class, use an absolute path. For example, if the current class is in the package org.jamesd.examples.view, and we need to load a CSS file style.css which is in the package org.jamesd.examples.css, we have to use an absolute path:

     URL cssURL = getClass().getResource("/org/jamesd/examples/css/style.css");
     scene.getStylesheets().add(cssURL.toExternalForm());
    

    It's worth re-emphasizing for this example that the path "../css/style.css" does not contain valid Java resource names, and will not work if the application is bundled as a jar file.

Organizing code and resources

I recommend organizing your code and resources into packages determined by the part of the UI they are associated with. The following source layout in Eclipse gives an example of this organization:

enter image description here

Using this structure, each resource has a class in the same package, so it is easy to generate the correct URL for any resource:

FXMLLoader editorLoader = new FXMLLoader(EditorController.class.getResource("Editor.fxml"));
Parent editor = editorLoader.load();
FXMLLoader sidebarLoader = new FXMLLoader(SidebarController.class.getResource("Sidebar.fxml"));
Parent sidebar = sidebarLoader.load();

ImageView logo = new ImageView();
logo.setImage(newImage(SidebarController.class.getResource("logo.png").toExternalForm()));

mainScene.getStylesheets().add(App.class.getResource("style.css").toExternalForm());

If you have a package with only resources and no classes, for example, the images package in the layout below

enter image description here

you can even consider creating a "marker interface" solely for the purposes of looking up the resource names:

package org.jamesd.examples.sample.images ;
public interface ImageLocation { }

which now lets you find these resources easily:

Image clubs = new Image(ImageLocation.class.getResource("clubs.png").toExternalForm());

Loading resources from a subpackage of a class is also reasonably straightforward. Given the following layout:

enter image description here

we can load resources in the App class as follows:

package org.jamesd.examples.resourcedemo;

import java.net.URL;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class App extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {        
        
        URL fxmlResource = getClass().getResource("fxml/MainView.fxml");
        
        
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(fxmlResource);
        Parent root = loader.load();
        Scene scene = new Scene(root);
        scene.getStylesheets().add(getClass().getResource("style/main-style.css").toExternalForm());
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        Application.launch(args);
    }

}

To load resources which are not in the same package, or a subpackage, of the class from which you're loading them, you need to use the absolute path:

    URL fxmlResource = getClass().getResource("/org/jamesd/examples/resourcedemo/fxml/MainView.fxml");

Maven (and similar) standard layouts

Maven and other dependency management and build tools recommend a source folder layout in which resources are separated from Java source files. The Maven layout version of the previous example looks like:

enter image description here

It is important to understand how this is built to assemble the application:

  • *.java files in the source folder src/main/java are compiled to class files, which are deployed to the build folder or jar file.
  • Resources in the resource folder <c

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...