如何确定我的 JavaFX 应用程序所需的 FXML 文件、CSS 文件、图像和其他资源的正确路径? [英] How do I determine the correct path for FXML files, CSS files, Images, and other resources needed by my JavaFX Application?

查看:20
本文介绍了如何确定我的 JavaFX 应用程序所需的 FXML 文件、CSS 文件、图像和其他资源的正确路径?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的 JavaFX 应用程序需要能够找到 FXML 文件以使用 FXMLLoader 加载它们,以及样式表(CSS 文件)和图像.当我尝试加载这些时,我经常遇到错误,或者我尝试加载的项目在运行时根本没有加载.

对于 FXML 文件,我看到的错误消息包括

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

对于图像,堆栈跟踪包括

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

如何找出这些资源的正确资源路径?

解决方案

答案的简短版本:

  • 使用 getClass().getResource(...)SomeOtherClass.class.getResource(...) 创建一个 URL到资源
  • 将绝对路径(带有前导 /)或相对路径(没有前导 /)传递给 getResource(...)方法.路径是包含资源的,用.替换为/.
  • 不要在资源路径中使用 ...如果并且当应用程序捆绑为 jar 文件时,这将不起作用.如果资源不在同一个包或类的子包中,请使用绝对路径.
  • 对于 FXML 文件,将 URL 直接传递给 FXMLLoader.
  • 对于图像和样式表,在 URL 上调用 toExternalForm() 以生成要传递给 ImageStringcode> 或 ImageView 构造函数,或添加到 stylesheets 列表中.
  • 要进行故障排除,请检查您的 build 文件夹(或 jar 文件)的内容,而不是您的 source 文件夹.

完整答案

内容

  1. 此答案的范围
  2. 资源在运行时加载
  3. JavaFX 使用 URL 加载资源
  4. 资源名称规则
  5. 使用 getClass().getResource(...)
  6. 创建资源 URL
  7. 组织代码和资源
  8. Maven(和类似的)标准布局
  9. 问题排查

本回答的范围

请注意,此答案解决了作为应用程序一部分并与其捆绑在一起的加载资源(例如 FXML 文件、图像和样式表).因此,例如,加载用户从运行应用程序的机器上的文件系统中选择的图像将需要不同的技术,此处未涵盖.

资源在运行时加载

关于加载资源,首先要了解的是它们当然是在运行时加载的.通常,在开发过程中,应用程序是从文件系统运行的:也就是说,运行它所需的类文件和资源是文件系统上的单个文件.然而,一旦应用程序被构建,它通常是从一个 jar 文件中执行的.在这种情况下,FXML 文件、样式表和图像等资源不再是文件系统上的单个文件,而是 jar 文件中的条目.因此:

<块引用>

代码不能使用 FileFileInputStreamfile: URL 来加载资源

JavaFX 使用 URL 加载资源

JavaFX 使用 URL 加载 FXML、图像和 CSS 样式表.

FXMLLoader 明确期望将 java.net.URL 对象传递给它(或者传递给 static FXMLLoader.load(...) 方法、FXMLLoader 构造函数或 setLocation() 方法).

ImageScene.getStylesheets().add(...) 都期望 String 代表 URL.如果 URL 在没有方案的情况下传递,它们将相对于类路径进行解释.这些字符串可以通过在 URL 上调用 toExternalForm() 以稳健的方式从 URL 创建.

为资源创建正确 URL 的推荐机制是使用 Class.getResource(...),它在适当的 Class 实例上调用.这样的类实例可以通过调用getClass()(给出当前对象的类)或ClassName.class 来获得.Class.getResource(...) 方法接受一个表示资源名称的 String.

资源名称规则

  • 资源名称是以 / 分隔的路径名称.每个组件代表一个包或子包名称组件.
  • 资源名称区分大小写.
  • 资源名称中的各个组件必须是有效的 Java 标识符

最后一点有一个重要的后果:

<块引用>

... 不是有效的 Java 标识符,因此它们不能用于资源名称.

当应用程序从文件系统运行时,这些实际上可能工作,尽管这实际上更像是 getResource() 实现的意外.当应用程序捆绑为 jar 文件时,它们将失败.

同样,如果您在不区分大小写不同的文件名的操作系统上运行,那么在资源名称中使用错误的大小写可能会在从文件系统运行时起作用,但在从 jar 运行时会失败文件.

/开头的资源名称是绝对:换句话说,它们是相对于类路径解释的.没有前导 / 的资源名称相对于调用 getResource() 的类进行解释.

对此的一个细微变化是使用 getClass().getClassLoader().getResource(...).提供给 ClassLoader.getResource(...) 的路径不能/ 开头,并且总是绝对的,即它是相对于类路径.还需要注意的是,在模块化应用中,使用ClassLoader.getResource()访问资源,在某些情况下,需要遵守强封装规则,另外包含资源的包必须无条件打开.请参阅

使用这种结构,每个资源在同一个包中都有一个类,因此很容易为任何资源生成正确的 URL:

FXMLLoader editorLoader = new FXMLLoader(EditorController.class.getResource(Editor.fxml"));父编辑器 = editorLoader.load();FXMLLoader sidebarLoader = new FXMLLoader(SidebarController.class.getResource(Sidebar.fxml"));父侧边栏 = sidebarLoader.load();ImageView logo = new ImageView();logo.setImage(newImage(SidebarController.class.getResource("logo.png").toExternalForm()));mainScene.getStylesheets().add(App.class.getResource("style.css").toExternalForm());

如果你有一个只有资源没有类的包,例如下面布局中的images

您甚至可以考虑创建一个标记界面";仅用于查找资源名称:

package org.jamesd.examples.sample.images ;公共接口 ImageLocation { }

现在可以让您轻松找到这些资源:

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

从类的子包加载资源也相当简单.鉴于以下布局:

我们可以在App类中加载资源如下:

package org.jamesd.examples.resourcedemo;导入 java.net.URL;导入 javafx.application.Application;导入 javafx.fxml.FXMLLoader;导入 javafx.scene.Parent;导入 javafx.scene.Scene;导入 javafx.stage.Stage;公共类应用程序扩展应用程序{@覆盖public void start(Stage primaryStage) 抛出异常 {URL fxmlResource = getClass().getResource("fxml/MainView.fxml");FXMLLoader 加载器 = 新 FXMLLoader();loader.setLocation(fxmlResource);父根 = loader.load();场景场景 = 新场景(根);scene.getStylesheets().add(getClass().getResource("style/main-style.css").toExternalForm());primaryStage.setScene(场景);primaryStage.show();}公共静态无效主(字符串 [] args){Application.launch(args);}}

要加载不在加载它们的类的同一个包或子包中的资源,您需要使用绝对路径:

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

Maven(和类似的)标准布局

Maven 和其他依赖项管理和构建工具推荐使用 source 文件夹布局,其中资源与 Java 源文件分开.上一个示例的 Maven 布局版本如下所示:

了解它是如何构建以组装应用程序很重要:

  • *.javasource文件夹中的src/main/java文件被编译成class文件,部署到build文件夹或 jar 文件.
  • resource 文件夹中的资源 src/main/resources复制到构建文件夹或 jar 文件中.

在此示例中,由于资源位于与定义源代码的包的子包相对应的文件夹中,因此生成的构建(在 Maven 中默认位于 target/classes) 由单个结构组成.

请注意,src/main/javasrc/main/resources 都被视为构建中相应结构的根,因此只有它们的内容,而不是文件夹本身是构建的一部分.换句话说,在运行时没有可用的 resources 文件夹.下面的故障排除"中显示了构建结构.部分.

请注意,本例中的 IDE (Eclipse) 显示的 src/main/java 源文件夹与 src/main/resources 文件夹不同;在第一种情况下,它显示 packages,但对于资源文件夹,它显示 folders.确保您知道您是否正在创建包(其名称以 . 分隔)或文件夹(其名称不得包含 .,或任何其他在 Java 中无效的字符标识符)在您的 IDE 中.

故障排除

如果出现意外错误,请先检查以下内容:

  • 确保您没有为资源使用无效名称.这包括在资源路径中使用 ....
  • 确保在预期的地方使用相对路径,在预期的地方使用绝对路径.对于 Class.getResource(...) 路径是绝对的,如果它有一个前导的 / ,否则是相对的.对于ClassLoader.getResource(...),路径总是绝对的,并且不能/开头.
  • 请记住,绝对路径是相对于 classpath 定义的.通常,类路径的根是 IDE 中所有源文件夹和资源文件夹的联合.

如果所有这些看起来都正确,但您仍然看到错误,请检查 build 或部署文件夹.此文件夹的确切位置因 IDE 和构建工具而异.如果您使用 Maven,默认情况下它是 target/classes.其他构建工具和 IDE 将部署到名为 binclassesbuildout 的文件夹.

通常,您的 IDE 不会显示构建文件夹,因此您可能需要使用系统文件资源管理器进行检查.

上面 Maven 示例的组合源和构建结构是

如果您要生成 jar 文件,某些 IDE 可能允许您在树视图中展开 jar 文件以检查其内容.您还可以使用 jar tf file.jar 从命令行检查内容:

$ jar -tf resource-demo-0.0.1-SNAPSHOT.jar元信息/元信息/清单.MF组织/组织/詹姆斯/组织/詹姆斯/例子/org/jamesd/examples/resourcedemo/org/jamesd/examples/resourcedemo/images/org/jamesd/examples/resourcedemo/style/org/jamesd/examples/resourcedemo/fxml/org/jamesd/examples/resourcedemo/images/so-logo.pngorg/jamesd/examples/resourcedemo/style/main-style.cssorg/jamesd/examples/resourcedemo/Controller.classorg/jamesd/examples/resourcedemo/fxml/MainView.fxmlorg/jamesd/examples/resourcedemo/App.class模块信息.class元信息/行家/META-INF/maven/org.jamesd.examples/META-INF/maven/org.jamesd.examples/resource-demo/META-INF/maven/org.jamesd.examples/resource-demo/pom.xmlMETA-INF/maven/org.jamesd.examples/resource-demo/pom.properties$

如果资源没有被部署,或者被部署到一个意外的位置,请检查你的构建工具或 IDE 的配置.

外部教程参考

关于资源位置的一个有用的外部教程是 Eden coding's tutorial:

伊甸园编码教程的好处是它很全面.除了涵盖此问题中有关从 Java 代码查找的信息之外.Eden 教程涵盖的主题包括定位在 CSS 中编码为 url 的资源,或使用 @ 说明符或 fx:include 元素在 FXML 中的资源引用(这些是当前的主题)本答案未直接涵盖).

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?

解决方案

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 build 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:

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

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:

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:

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 src/main/resources are copied to the build folder or jar file.

In this example, because the resources are in folders that correspond to subpackages of the packages where the source code is defined, the resulting build (which, by default with Maven, is in target/classes) consists of a single structure.

Note that both src/main/java and src/main/resources are considered the root for the corresponding structure in the build, so only their content, not the folders themselves, are part of the build. In other words, there is no resources folder available at runtime. The build structure is shown below in the "troubleshooting" section.

Notice that the IDE in this case (Eclipse) displays the src/main/java source folder differently to the src/main/resources folder; in the first case it displays packages, but for the resource folder it displays folders. Make sure you know if you are creating packages (whose names are .-delimited) or folders (whose names must not contain ., or any other character not valid in a Java identifier) in your IDE.

Troubleshooting

If you get errors you do not expect, first check the following:

  • Make sure you are not using invalid names for your resources. This includes using . or .. in the resource path.
  • Make sure you are using relative paths where expected, and absolute paths where expected. for Class.getResource(...) the path is absolute if it has a leading /, and relative otherwise. For ClassLoader.getResource(...), the path is always absolute, and must not start with a /.
  • Remember that absolute paths are defined relative to the classpath. Typically the root of the classpath is the union of all source and resource folders in your IDE.

If all this seems correct, and you still see errors, check the build or deployment folder. The exact location of this folder will vary by IDE and build tool. If you are using Maven, by default it is target/classes. Other build tools and IDEs will deploy to folders named bin, classes, build, or out.

Often, your IDE will not show the build folder, so you may need to check it with the system file explorer.

The combined source and build structure for the Maven example above is

If you are generating a jar file, some IDEs may allow you to expand the jar file in a tree view to inspect its contents. You can also check the contents from the command line with jar tf file.jar:

$ jar -tf resource-demo-0.0.1-SNAPSHOT.jar 
META-INF/
META-INF/MANIFEST.MF
org/
org/jamesd/
org/jamesd/examples/
org/jamesd/examples/resourcedemo/
org/jamesd/examples/resourcedemo/images/
org/jamesd/examples/resourcedemo/style/
org/jamesd/examples/resourcedemo/fxml/
org/jamesd/examples/resourcedemo/images/so-logo.png
org/jamesd/examples/resourcedemo/style/main-style.css
org/jamesd/examples/resourcedemo/Controller.class
org/jamesd/examples/resourcedemo/fxml/MainView.fxml
org/jamesd/examples/resourcedemo/App.class
module-info.class
META-INF/maven/
META-INF/maven/org.jamesd.examples/
META-INF/maven/org.jamesd.examples/resource-demo/
META-INF/maven/org.jamesd.examples/resource-demo/pom.xml
META-INF/maven/org.jamesd.examples/resource-demo/pom.properties
$ 

If the resources are not being deployed, or are being deployed to an unexpected location, check the configuration of your build tool or IDE.

External Tutorial Reference

A useful external tutorial for resource location is Eden coding's tutorial:

The nice thing about the Eden coding tutorial is that it is comprehensive. In addition to covering the information on lookups from Java code which is in this question. The Eden tutorial covers topics such as locating resources that are encoded as urls in CSS, or resource references in FXML using an @ specifier or fx:include element (which are topics currently not directly covered in this answer).

这篇关于如何确定我的 JavaFX 应用程序所需的 FXML 文件、CSS 文件、图像和其他资源的正确路径?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆