JavaFX: пользовательский компонент создает исключение LoadException только в Runnable JAR - PullRequest
2 голосов
/ 02 июля 2019

Я пытаюсь сгенерировать исполняемый jar-файл из проекта JavaFX, над которым я работаю: работает нормально при выполнении в Eclipse IDE. Но при выполнении из сгенерированного файла JAR возникает ошибка времени выполнения, когда FXML пытается загрузить пользовательский компонент (класс, который extends компонент JFX BorderPane). Что не так?

Вот файл FXML, где используется пользовательский класс CardPane, который расширяет BorderPane.

<?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' Никогда в 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

1 Ответ

2 голосов
/ 02 июля 2019

@ Zephyr указал, как я пропустил утверждение «Причины:» в StackTrace при написании поста.Это ясно указывает на то, что экземпляр ImageView не может быть создан из-за неверно сформированного URL.

Хитрость в том, что у меня была следующая константа, указывающая на корневой путь, где находятся мои ресурсы изображений:

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 на "/".

Как сказано в этой статье , при работе с ресурсами следует избегать File.separator:

" [...] использовать системно-зависимыеразделитель файлов только при работе с файлами и при отображении пути к пользователю. Для всех остальных случаев используйте косую черту."

...