我正在尝试从我正在处理的 JavaFX 项目生成一个可执行 jar 文件,该文件在通过 Eclipse IDE 执行时运行正常。
但是,当从生成的 jar 文件执行时,当 FXML
尝试加载自定义组件(扩展
JFX 组件 BorderPane
的类)时,会发生运行时错误>)。
怎么了?
这是 FXML
文件,其中使用了扩展 BorderPane
的自定义类 CardPane
。
<?xml version="1.0" encoding="UTF-8"?>
<?import com.tsi.ui.CardPane?>
<?import java.lang.*?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>
<?import com.tsi.ui.CardPane?>
<BorderPane fx:id="mainPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="500.0" prefWidth="400.0" style="-fx-background-color: #454449;" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<center>
<GridPane fx:id="gridPane" opacity="0.99" prefHeight="386.0" prefWidth="380.0" BorderPane.alignment="CENTER">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<!--
The below command (referenced in the stack trace as line 40,
which here is not precise, since I've deleted some useless
stuff for the post propose) is where the error occurs
-->
<CardPane fx:id="pane00" prefHeight="200.0" prefWidth="200.0" />
<CardPane fx:id="pane01" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" />
<CardPane fx:id="pane02" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="2" />
<CardPane fx:id="pane10" prefHeight="200.0" prefWidth="200.0" GridPane.rowIndex="1" />
<CardPane fx:id="pane11" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<CardPane fx:id="pane12" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="2" GridPane.rowIndex="1" />
<CardPane fx:id="pane20" prefHeight="200.0" prefWidth="200.0" GridPane.rowIndex="2" />
<CardPane fx:id="pane21" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="2" />
<CardPane fx:id="pane22" prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="2" GridPane.rowIndex="2" />
</children>
</GridPane>
</center>
<!-- I will show only the interesting point -->
...
</BorderPane>
CardPane 只是用作扩展 BorderPane 的类,其布局位置已填充了一些其他节点。在此代码中,我们使用其中一些填充 GridPane 单元格的内容。
以下几行显示了它的定义方式:
public class CardPane extends BorderPane {
private Card card;
private Label cardName;
private Label cardValue;
private ImageView valueIcon;
private HBox hBox;
private Node armaSprite;
public CardPane() {
hBox = new HBox();
cardName = new Label();
cardValue = new Label();
valueIcon = new ImageView(new Image(Sprite.CAMINHO + File.separator + "CoracaoIcon.png", 27, 22, false, false));
}
老实说,我不知道这种方法(扩展 BorderPane
类,以这种方式制作我自己的组件)是否是一个好的实践。如果没有,如果有人能让我知道应该避免什么,我会很高兴。
但最重要的是,要点是,在 Eclipse 中一切正常,但在导出项目时却不然。为什么?
这是 Windows 控制台上打印的异常。同样,仅当使用 java.exe -jar '.\Dungeon Cards.jar' 执行 jar 时才会发生这种情况。从来没有在 IDE 上。
javafx.fxml.LoadException:
file:/C:/Users/ramon/Desktop/Dungeon%20Cards.jar!/com/tsi/ui/fxml/game.fxml:40
at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2601)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2579)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3214)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3175)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3148)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3124)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3104)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:3097)
at com.tsi.app.Jogo.<init>(Jogo.java:40)
at com.tsi.app.DungeonCards.iniciarJogo(DungeonCards.java:138)
at com.tsi.ui.Instrucoes.esconder(Instrucoes.java:72)
at com.tsi.ui.Instrucoes$2.handle(Instrucoes.java:52)
at com.tsi.ui.Instrucoes$2.handle(Instrucoes.java:1)
at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Scene$KeyHandler.process(Scene.java:3964)
at javafx.scene.Scene$KeyHandler.access$1800(Scene.java:3910)
at javafx.scene.Scene.impl_processKeyEvent(Scene.java:2040)
at javafx.scene.Scene$ScenePeerListener.keyEvent(Scene.java:2501)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:217)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:149)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleKeyEvent$352(GlassViewEventHandler.java:248)
at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleKeyEvent(GlassViewEventHandler.java:247)
at com.sun.glass.ui.View.handleKeyEvent(View.java:546)
at com.sun.glass.ui.View.notifyKey(View.java:966)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$147(WinApplication.java:177)
at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.IllegalArgumentException: Invalid URL: Invalid URL or resource not found
at javafx.scene.image.Image.validateUrl(Image.java:1118)
at javafx.scene.image.Image.<init>(Image.java:659)
at com.tsi.ui.CardPane.<init>(CardPane.java:42)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
at java.lang.reflect.Constructor.newInstance(Unknown Source)
at java.lang.Class.newInstance(Unknown Source)
at sun.reflect.misc.ReflectUtil.newInstance(Unknown Source)
at javafx.fxml.FXMLLoader$InstanceDeclarationElement.constructValue(FXMLLoader.java:1009)
at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:746)
at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2707)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2527)
... 39 more
Caused by: java.lang.IllegalArgumentException: Invalid URL or resource not found
at javafx.scene.image.Image.validateUrl(Image.java:1110)
... 51 more
最佳答案
@Zephyr指出我在写这篇文章时如何错过了 StackTrace 上的“原因:”声明。它清楚地表明由于 URL 格式错误,无法实例化 ImageView
。
诀窍是我有以下常量指向图像资源所在的根路径:
public static final String CAMINHO = "/com/tsi/sprites";
URL 被定义为 CAMINHO + File.separator + [image_file].png
结果:“/com/tsi/sprites\[image_file].png”
构造函数串联中使用的 File.separator
保留 Windows 文件分隔模式(反斜杠),而常量值 CAMINHO
使用普通斜杠(Linux 模式)。
不知何故,Eclipse 可以抽象出错误的组合,但在它之外运行时,Windows 却不能。
为了解决这个问题,我只是将 File.separator
更改为 "/"
。
正如this article中所述,在使用资源时,应避免使用 File.separator
:
“[...]仅在处理文件以及向用户显示路径时才使用系统特定的文件分隔符。对于所有其他情况,请使用正斜杠。”
关于JavaFX:自定义组件仅在 Runnable JAR 中抛出 LoadException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56844078/