8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

如何确定 JavaFX 应用程序所需的 FXML 文件、CSS 文件、图像和其他资源的正确路径?

Machou 1月前

59 0

我的 JavaFX 应用程序需要能够找到 FXML 文件,以便使用 FXMLLoader 加载它们,以及样式表(CSS 文件)和图像。当我尝试加载这些文件时,我经常会收到错误,或者 i...

我的 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

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

帖子版权声明 1、本帖标题:如何确定 JavaFX 应用程序所需的 FXML 文件、CSS 文件、图像和其他资源的正确路径?
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由Machou在本站《maven》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 您可以考虑对使用 classloader api 的查找进行一些更改:也许强调它不能有前导斜杠(正如 jewelsea 在您的另一个答案中的评论所指出的那样 .com/a/68913233/203657)

  • @kleopatra 一些框架(例如 afterburner.fx 和 FXWeaver)要求控制器类名与 FXML 名称匹配,这会强制 FXML 名称为大写。

  • 很好地补充了提到大小写处理中的潜在差异 - 这就是为什么我更喜欢将所有内容都使用小写资源名称(尽管这不是命名约定)

  • 答案简短版:

    • 使用 getClass().getResource(...) SomeOtherClass.class.getResource(...) 创建 URL 资源
    • 将绝对路径(以 开头 / )或相对路径(不以 开头 / )传递给该 getResource(...) 方法。路径是 包含资源的 . 替换为 / .
    • 不要 .. 在资源路径中使用。如果应用程序被打包为 jar 文件,则此方法无效。如果资源不在同一个包中或类的子包中,请使用绝对路径。
    • 对于 FXML 文件, URL 直接传递给 FXMLLoader .
    • 对于图像和样式表,调用 toExternalF或m() URL 生成 String 传递给 Image or ImageView 构造函数,或添加到 stylesheets 列表中。
    • 构建 文件夹(或 jar 文件) 的内容文件夹。
    • 获取资源时 src 放置 总是 错误的。该 src 目录仅在开发和构建时可用,而不是在部署和运行时可用。

    完整答案

    内容

    1. 本答案的范围
    2. 资源在运行时加载
    3. JavaFX 使用 URL 加载资源
    4. 资源流
    5. 资源名称规则
    6. 使用以下方式创建资源 URL getClass().getResource(...)
    7. 组织代码和资源
    8. Maven(及类似)标准布局
    9. 故障排除

    本答案的范围

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

    资源在运行时加载

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

    代码无法使用 File , FileInputStream file: URL 加载资源

    JavaFX 使用 URL 加载资源

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

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

    Image Scene.getStylesheets().add(...) 要求 String 表示 URL。如果传递的 URL 没有方案,则它们将相对于类路径进行解释。可以 URL 通过 toExternalForm() 调用 URL .

    为资源创建正确 URL 的推荐机制是使用 Class.getResource(...) ,它在适当的实例上调用 Class (给出当前对象的类)或 来 getClass() 获取此类实例 ClassName.class 。该 Class.getResource(...) 方法采用 String 表示资源名称的 。

    资源流

    如果您需要将资源作为流,则可以使用 getClass().getResourceAsStream(...) SomeOtherClass.class.getResourceAsStream(...) 为资源数据创建流。但是,对于 JavaFX 中的大多数工作,您不需要这样做,因为 JavaFX API 主要使用 URL 作为输入,而不是流。有些 API(例如 Image 构造函数和 FXMLLoader)可以同时使用 URL 和流。对于此类 API,通常最好使用基于 URL 的 API 形式,而不是基于流的 API 形式。这允许 FXMLLoader 等进程找到包含资源的相对 URL,而如果仅提供原始 InputStream,则无法做到这一点。

    资源名称规则

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

    最后一点有一个重要的结论:

    . 并且 .. 不是有效的 Java 标识符,因此 不能在资源名称中使用 .

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

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

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

    对此的一个轻微变化是使用 getClass().getClassLoader().getResource(...) 。提供给 的路径 ClassLoader.getResource(...) 不能 以 开头 / 始终绝对路径,即相对于类路径。还应注意,在模块化应用程序中,使用 访问资源 ClassLoader.getResource() 在某些情况下受强封装规则的约束,此外,必须无条件打开包含资源的包。有关详细信息,请参阅 文档

    使用以下方式创建资源 URL getClass().getResource()

    要创建资源 URL,请使用 someClass.getResource(...) 。通常, someClass 表示当前对象的类,并使用 获取 getClass() 。但是,情况不一定如此,如下一节所述。

    • 第15页

       // 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());
    • p16

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

      p17

    组织代码和资源

    我建议将代码和资源组织到与它们关联的 UI 部分确定的包中。Eclipse 中的以下源代码布局给出了这种组织的示例:

    enter image description here

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

    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());
    

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

    enter image description here

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

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

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

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

    从类的子包中加载资源也相当简单。给定以下布局:

    enter image description here

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

    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");
            
            Parent root = FXMLLoader.load(fxmlResource);
            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);
        }
    }
    

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

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

    Maven(及类似)标准布局

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

    enter image description here

    了解如何构建这个组件对于组装应用程序非常重要:

    • *.java 文件夹 中的文件 src/main/java 被编译为类文件,并部署到构建文件夹或jar文件中。
    • 资源 文件夹 src/main/resources 中的资源 复制 到构建文件夹或者jar文件中。

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

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

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

    如果您正在使用 Maven,并且为了便于维护,您决定将 .fxml 文件放在引用它们的 .java 文件旁边(而不是严格遵循 Maven 标准目录布局 在 pom.xml 文件中 包含 类似以下内容

        <build>
            ...
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.fxml</include>
                    <include>**/*.css</include>
                </includes>
            </resource>
            ...
        </build>
    

    如果您这样做,那么您可以使用一种方法, FXMLLoader.load(getClass().getResource("MyFile.fxml")) 让您的类从包含其自己的.class 文件的目录中加载.fxml 资源。

    故障排除

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

    • 确保您没有对资源使用无效的名称。这包括 . 在资源路径中 ..
    • 确保在需要的地方使用相对路径,在需要的地方使用绝对路径。对于 Class.getResource(...) 路径,如果以 开头 / ,则为绝对路径,否则为相对路径。对于 ClassLoader.getResource(...) ,路径始终是绝对路径,并且 不能 以 开头 / .
    • 请记住,绝对路径是相对于 classpath 。通常,classpath 的根是 IDE 中所有源和资源文件夹的并集。

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

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

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

    enter image description here

    如果您正在生成 jar 文件,某些 IDE 可能允许您在树视图中展开 jar 文件以检查其内容。您还可以使用以下命令从命令行检查内容 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
    $ 
    

    如果资源未被部署或者被部署到意外位置,请检查构建工具或 IDE 的配置。

    图像加载故障排除代码示例

    此代码故意比严格意义上更冗长,以便于为图像加载过程添加额外的调试信息。它还使用 System.out 而不是记录器,以便于移植。

    String resourcePathString = "/img/wumpus.png";
    Image image = loadImage(resourcePathString);
    
    // ...
    
    private Image loadImage(String resourcePathString) {
        System.out.println("Attempting to load an image from the resourcePath: " + resourcePathString);
        URL resource = HelloApplication.class.getResource(resourcePathString);
        if (resource == null) {
            System.out.println("Resource does not exist: " + resourcePathString);
    
            return null;
        }
    
        String path = resource.toExternalForm();
        System.out.println("Image path: " + path);
    
        Image image = new Image(path);
        System.out.println("Image load error?  " + image.isError());
        System.out.println("Image load exception? " + image.getException());
    
        if (!image.isError()) {
            System.out.println("Successfully loaded an image from " + resourcePathString);
        }
    
        return image;
    }
    

    外部教程参考

    关于资源定位的一个有用的外部教程是 Eden coding 的教程:

    • JavaFX 中资源文件放在哪里

    Eden 编码教程的优点在于它很全面。除了涵盖此问题中有关从 Java 代码查找的信息之外。Eden 教程涵盖了诸如定位在 CSS 中编码为 URL 的资源,或使用说明 @ 符或 fx:include 元素在 FXML 中引用资源等主题(这些主题目前未在此答案中直接涵盖)。

  • 另外,您可以检查包含所有已编译类的目标目录。有时,IDE 不想编译这些文件,因此您无法在运行时获取它们。

  • 非常好的想法和答案:) 已添加到标签 wiki 中的常见问题解答中,以便我们可以轻松找到重复的闭包。

  • 由于 JavaFX 标签上有很多关于加载资源的问题,我将此问答发布为社区 Wiki。如果您觉得问题或答案可以改进,请对其进行编辑。

返回
作者最近主题: