簡體   English   中英

如何確定我的 JavaFX 應用程序所需的 FXML 文件、CSS 文件、圖像和其他資源的正確路徑?

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

我的 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(...)方法。 路徑是包含資源的package ,帶有. 替換為/
  • 不要在資源路徑中使用.. 如果並且當應用程序捆綁為 jar 文件時,這將不起作用。 如果資源不在同一個 package 或 class 的子包中,請使用絕對路徑。
  • 對於 FXML 文件,將URL直接傳遞給FXMLLoader
  • 對於圖像和樣式表,在URL上調用toExternalForm()以生成要傳遞給ImageImageView構造函數的String ,或添加到stylesheets列表。
  • 要進行故障排除,請檢查構建文件夾(或 jar 文件)的內容,而不是文件夾。

完整答案

內容

  1. 這個答案的 Scope
  2. 資源在運行時加載
  3. JavaFX 使用 URL 加載資源
  4. 資源名稱規則
  5. 使用getClass().getResource(...)創建資源 URL
  6. 組織代碼和資源
  7. Maven(和類似的)標准布局
  8. 故障排除

這個答案的 Scope

請注意,此答案涉及加載資源(例如 FXML 文件、圖像和樣式表),這些資源是應用程序的一部分,並與應用程序捆綁在一起。 因此,例如,加載用戶從運行應用程序的機器上的文件系統中選擇的圖像將需要不同的技術,此處未涉及。

資源在運行時加載

關於加載資源,首先要了解的是,它們當然是在運行時加載的。 通常,在開發過程中,應用程序從文件系統運行:即 class 文件和運行它所需的資源是文件系統上的單個文件。 但是,一旦構建了應用程序,它通常會從 jar 文件執行。 在這種情況下,FXML 文件、樣式表和圖像等資源不再是文件系統上的單個文件,而是 jar 文件中的條目。 所以:

代碼不能使用FileFileInputStreamfile: URL 來加載資源

JavaFX 使用 URL 加載資源

JavaFX 使用 URL 加載 FXML、圖像和 CSS 樣式表。

FXMLLoader明確要求將java.net.URL object 傳遞給它(或者傳遞給static FXMLLoader.load(...)方法,或者傳遞給FXMLLoader方法setLocation()

ImageScene.getStylesheets().add(...)都期望String代表 URL。 如果 URL 是在沒有方案的情況下傳遞的,則它們將相對於類路徑進行解釋。 這些字符串可以通過調用 URL 上的URL toExternalForm()以穩健的方式從URL創建。

為資源創建正確的 URL 的推薦機制是使用Class.getResource(...) ,它在適當的Class實例上調用。 這樣的 class 實例可以通過調用getClass() (它給出當前對象的 class)或ClassName.class來獲得。 Class.getResource(...)方法采用一個表示資源名稱的String

資源名稱規則

  • 資源名稱是/分隔的路徑名稱。 每個組件代表一個 package 或子包名稱組件。
  • 資源名稱區分大小寫。
  • 資源名稱中的各個組件必須是有效的 Java 標識符

最后一點有一個重要的后果:

. ..不是有效的 Java 標識符,因此它們不能用於資源名稱中。

當應用程序從文件系統運行時,這些實際上可能會起作用,盡管這實際上更像是getResource()實現的意外。 當應用程序捆綁為 jar 文件時,它們將失敗。

同樣,如果您在不區分僅大小寫不同的文件名的操作系統上運行,則在從文件系統運行時在資源名稱中使用錯誤的大小寫可能會起作用,但在從 jar 文件運行時會失敗。

/開頭的資源名稱是絕對的:換句話說,它們是相對於類路徑解釋的。 沒有前導/的資源名稱相對於調用getResource()的 class 進行解釋。

對此略有不同的是使用getClass().getClassLoader().getResource(...) 提供給ClassLoader.getResource(...)的路徑不能/開頭,並且始終是絕對的,即它是相對於類路徑的。 還需要注意的是,在模塊化應用程序中,使用ClassLoader.getResource()訪問資源,在某些情況下,受強封裝規則的約束,另外必須無條件打開包含該資源的 package。 有關詳細信息,請參閱文檔

使用getClass().getResource()創建資源 URL

要創建資源 URL,請使用someClass.getResource(...) 通常, someClass表示當前 object 的 class,使用getClass()獲取。 但是,情況不一定如此,如下一節所述。

  • 如果資源與當前 class 位於同一 package 中,或者在該 class 的子包中,請使用資源的相對路徑:

     // 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());
  • 如果資源位於不是當前 class 子包的 package 中,請使用絕對路徑。 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.ZC7A62 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());

    對於此示例,值得再次強調的是,路徑"../css/style.css"不包含有效的 Java 資源名稱,並且如果應用程序捆綁為 jar 文件,則該路徑將不起作用

組織代碼和資源

我建議將您的代碼和資源組織到由它們關聯的 UI 部分確定的包中。 Eclipse 中的以下源代碼布局給出了這種組織的示例:

在此處輸入圖像描述

使用這種結構,每個資源在同一個package中都有一個class,因此很容易為任何資源生成正確的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());

如果你有一個只有資源沒有類的 package,例如,下面布局中的images package

在此處輸入圖像描述

您甚至可以考慮創建一個“標記界面”,僅用於查找資源名稱:

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

現在,您可以輕松找到這些資源:

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

從 class 的子包加載資源也相當簡單。 給定以下布局:

在此處輸入圖像描述

我們可以在App class 中加載資源如下:

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

}

要加載不在同一個 package 或 class 的子包中的資源,您需要使用絕對路徑:

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

Maven(和類似的)標准布局

Maven 和其他依賴管理和構建工具建議使用文件夾布局,其中資源與 Java 源文件分開。 上一個示例的 Maven 布局版本如下所示:

在此處輸入圖像描述

重要的是要了解它是如何構建來組裝應用程序的:

  • *.java文件夾src/main/java中的文件編譯為 class 文件,部署到 build 文件夾或 jar 文件中。
  • 資源文件夾src/main/resources中的資源復制到構建文件夾或 jar 文件中。

在此示例中,由於資源位於與定義源代碼的包的子包相對應的文件夾中,因此生成的構建(默認情況下,Maven 位於target/classes中)由單個結構組成。

請注意, src/main/javasrc/main/resources都被視為構建中相應結構的根,因此只有它們的內容,而不是文件夾本身,是構建的一部分。 換句話說,在運行時沒有可用的resources文件夾。 構建結構如下面的“疑難解答”部分所示。

請注意,在這種情況下(Eclipse)中的 IDE 顯示src/main/java源文件夾與src/main/resources文件夾不同; 在第一種情況下,它顯示,但對於資源文件夾,它顯示文件夾 確保您知道您是在. .

故障排除

如果您遇到意外的錯誤,請首先檢查以下內容:

  • 確保您沒有為您的資源使用無效名稱。 這包括使用. ..在資源路徑中。
  • 確保您在預期的地方使用相對路徑,在預期的地方使用絕對路徑。 對於Class.getResource(...)路徑是絕對的,如果它有一個前導/ ,否則是相對的。 對於ClassLoader.getResource(...) ,路徑始終是絕對的,並且不能/開頭。
  • 請記住,絕對路徑是相對於路徑定義的。 通常,類路徑的根是 IDE 中所有源文件夾和資源文件夾的聯合。

如果所有這些看起來都正確,但您仍然看到錯誤,請檢查構建或部署文件夾。 此文件夾的確切位置因 IDE 和構建工具而異。 如果您使用的是 Maven,則默認為target/classes 其他構建工具和 IDE 將部署到名為binclassesbuildout的文件夾中。

通常,您的 IDE 不會顯示構建文件夾,因此您可能需要使用系統文件資源管理器進行檢查。

上面的 Maven 示例的組合源和構建結構是

在此處輸入圖像描述

如果您正在生成 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's tutorial:

Eden 編碼教程的好處是它很全面。 除了涵蓋來自此問題中的 Java 代碼的查找信息之外。 Eden 教程涵蓋了一些主題,例如在 CSS 中定位編碼為 url 的資源,或使用@說明符或fx:include元素在 FXML 中的資源引用(這些主題目前未直接包含在此答案中)。

請問,如果 Controller.java 位於“java.org.jamesd.examples.resourcedemo”中,想要加載位於“resources.org.jamesd.examples.resourcedemo.fxml”中的 MainView.fxml 文件,

在 Controller.java 中引用 MainView.fxml 的正確文件路徑是什么

root = FXMLLoader.load(Main.class.getResource("文件路徑"));

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM