JavaFX InvocationTargetException при нажатии кнопки в панели

Я просто пытаюсь нарисовать линию из другого класса в моем проекте JavaFX (в качестве начала домашней работы). Но я столкнулся с этим InvocationTargetException вместе с исключением NullPointerException.

"C:\Program Files\Java\jdk1.8.0_144\bin\java" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2017.2.4\lib\idea_rt.jar=58260:C:\Program Files\JetBrains\IntelliJ IDEA 2017.2.4\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;C:\Users\caspe\Desktop\myFinalMiniProject\out\production\myFinalMiniProject" sample.Main
drawing maze..
Exception in thread "JavaFX Application Thread" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
    at javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1774)
    at javafx.fxml.FXMLLoader$ControllerMethodEventHandler.handle(FXMLLoader.java:1657)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
    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.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:49)
    at javafx.event.Event.fireEvent(Event.java:198)
    at javafx.scene.Node.fireEvent(Node.java:8413)
    at javafx.scene.control.Button.fire(Button.java:185)
    at com.sun.javafx.scene.control.behavior.ButtonBehavior.mouseReleased(ButtonBehavior.java:182)
    at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(BehaviorSkinBase.java:96)
    at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(BehaviorSkinBase.java:89)
    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.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$MouseHandler.process(Scene.java:3757)
    at javafx.scene.Scene$MouseHandler.access$1500(Scene.java:3485)
    at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1762)
    at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2494)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:381)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$354(GlassViewEventHandler.java:417)
    at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:416)
    at com.sun.glass.ui.View.handleMouseEvent(View.java:555)
    at com.sun.glass.ui.View.notifyMouse(View.java:937)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
    at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at sun.reflect.misc.Trampoline.invoke(MethodUtil.java:71)
    at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at sun.reflect.misc.MethodUtil.invoke(MethodUtil.java:275)
    at javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1769)
    ... 48 more
Caused by: java.lang.NullPointerException
    at sample.KrushkalA.drawMaze(KrushkalA.java:23)
    at sample.Controller.doKruskal(Controller.java:13)
    ... 58 more

Я пробовал этот ответ от James_D, но он все равно не запускается после пробуя его решение.

Главный:

public void start(Stage primaryStage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
        primaryStage.setTitle("Hello World");
        primaryStage.setScene(new Scene(root, 350, 350));
        primaryStage.show();
    }

FXML:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.canvas.Canvas?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.Pane?>

<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="350.0"
      prefWidth="350.0" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
    <children>
        <Button fx:id="buttonKruskal" layoutX="14.0" layoutY="2.0" mnemonicParsing="false" onAction="#doKruskal"
                prefHeight="20.0" prefWidth="100.0" text="Kruskal"/>
        <Button fx:id="buttonRecursiveD" layoutX="236.0" layoutY="2.0" mnemonicParsing="false" onAction="#doRecursiveD"
                prefHeight="20.0" prefWidth="100.0" text="RecursiveD"/>
        <Button fx:id="buttonRecursiveB" layoutX="125.0" layoutY="2.0" mnemonicParsing="false" onAction="#doRecursiveB"
                prefHeight="20.0" prefWidth="100.0" text="RecursiveB"/>
        <Canvas height="300.0" layoutX="25.0" layoutY="36.0" width="300.0"/>
    </children>
</Pane>

Контроллер:

public void doKruskal(ActionEvent event) {
    System.out.println("drawing maze..");
    KrushkalA kruAlg = new KrushkalA();

    kruAlg.drawMaze();

    // kruAlg.solveMaze();

}

КрушкалА класс:

    @FXML
    Canvas canvas;
    GraphicsContext gc;


    public void drawMaze(){
        gc = canvas.getGraphicsContext2D();
        gc.strokeLine(0,0,300,50);
    }

Возможно, это то, что я пытаюсь сделать; для запуска метода в классе KrushkalA из контроллера, который рисует линию или просто делает что-то внутри холста при нажатии кнопки.

Это неправильный способ сделать это? Я могу запустить код, вызывающий исключение, в моем классе Controller, но не в каком-либо другом классе? Пример кода будет высоко оценен.


person Casper Hansen    schedule 05.04.2018    source источник
comment
@FXML-аннотированные поля инициализируются только в контроллере. kruAlg не является контроллером, поэтому canvas никогда не инициализируется. Не совсем понятно, что вы намереваетесь или почему вы делегируете методы типа контроллера другим объектам. Почему бы вам просто не поместить метод drawMaze() в контроллер, которому он, естественно, принадлежит?   -  person James_D    schedule 05.04.2018
comment
Также неясно, какое отношение связанный вопрос имеет к этому вопросу. Связанный вопрос даже не имеет исключения нулевого указателя.   -  person James_D    schedule 05.04.2018
comment
Итак, суть моего задания такова: внедрить хотя бы один из алгоритмов (Крускала, рекурсивного поиска с возвратом, рекурсивного деления) в программу JavaFX. Вот почему я хочу вывести его за пределы контроллера, что я считал возможным, а может быть, и нет?   -  person Casper Hansen    schedule 06.04.2018
comment
Извините за другой пост, я просто искал много сообщений, но не смог найти решение, поэтому я публикую это. Возможно, я неправильно понял, как должен использоваться контроллер. Я мог бы, конечно, запустить все три алгоритма в контроллере, но я просто подумал, что было бы неплохо с разными классами.   -  person Casper Hansen    schedule 06.04.2018
comment
Однако реализация алгоритма не должна иметь ничего общего с пользовательским интерфейсом (т. е. реализация алгоритма не должна зависеть от его визуализации). Так что либо ваш метод drawMaze() возвращает некоторый объект, описывающий результат выполнения алгоритма (я не знаком с этими алгоритмами), либо, возможно, имеет обратные вызовы из алгоритма, которые вы можете подключить к контроллеру.   -  person James_D    schedule 06.04.2018
comment
Если вы действительно хотите, чтобы drawMaze() рисовал на холсте, внедрите холст в контроллер (так же, как и другие ваши элементы FXML), а затем просто передайте холст методу drawMaze().   -  person James_D    schedule 06.04.2018


Ответы (1)


Причина, по которой ваш код не работает, заключается в том, что вы ожидаете, что canvas будет введен из FXML в ваш экземпляр KrushkalA. Просто это работает не так: @FXML-внедрение выполняется FXMLLoader при загрузке файла FXML, а любые @FXML-аннотированные поля в экземпляре контроллера инициализируются соответствующими элементами в файле FXML. с соответствием fx:id. Даже если вы добавите атрибут fx:id к холсту, определенному в FXML, FXMLLoader никак не сможет узнать об объекте KrushkalA (который даже не создан в этот момент, хотя это не помогло бы, если бы он был).

В любом случае, вся предпосылка здесь кажется ошибочной. Как правило, плохая идея разрешать ссылкам на ваши элементы пользовательского интерфейса выходить из контроллера. Предположим, в какой-то момент в будущем вы решите, что больше не хотите рисовать это с помощью Canvas (например, вы можете решить добавить Lines или какой-либо другой узел к Pane). В этом случае вам действительно нужно изменить только класс FXML и контроллера; если вы разрешили своему классу KrushkalA доступ к холсту, вам также нужно будет изменить это, и вам нужно будет посмотреть, передал ли этот класс ссылку на холст кому-либо еще. Поддерживать это становится намного сложнее, если вы нарушаете инкапсуляцию таким образом.

Если ваш класс KrushkalA действительно просто реализует алгоритм, он не должен иметь ничего общего с пользовательским интерфейсом. Я думаю, здесь вы создаете лабиринт (или, более абстрактно, остовное дерево для графа), поэтому ваш метод drawMaze() должен просто вычислить лабиринт и вернуть результат. По своей сути лабиринт — это просто набор границ (или ребер) между ячейками (узлами), поэтому я думаю, что я бы определил класс Boundary с некоторым простым представлением того, какая это граница (возможно, поля int x, int y и boolean horizontal, или что-то другое удобное); тогда у вас будет:

public class KrushkalA {

    public List<Boundary> drawMaze() {
        List<Boundary> maze = new ArrayList<>();
        // implementation of algorithm...
        return maze ;
    }
}

Затем, вернувшись в свой контроллер, вы должны сделать:

public class MazeController {

    @FXML
    private Canvas canvas ;

    @FXML
    private void doKrushkal(ActionEvent e) {
        KrushkalA kruAlg = new KrushkalA();
        List<Boundary> maze = kruAlg.drawMaze();
        // now render maze on canvas, using data in the List<Boundary> from above
        renderMaze(maze);
    }

    private void renderMaze(List<Boundary> maze) {
        // clear canvas, iterate through boundaries in maze,
        // and draw lines in appropriate place, etc.
    }
}

И, конечно же, добавьте fx:id="canvas" к элементу <Canvas> в FXML.

Этот подход правильно управляет разделением задач и соблюдает принцип единой ответственности: контроллер несет ответственность только за обновление пользовательского интерфейса и не занимается реализацией алгоритма, а класс KrushkalA несет ответственность только за реализацию алгоритма. и не заботится о том, как отображается пользовательский интерфейс (или даже если существует пользовательский интерфейс...).


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

    @FXML
    private void doKrushkal(ActionEvent e) {
        KrushkalA kruAlg = new KrushkalA();
        kruAlg.drawMaze(canvas);
    }

и

public class KrushkalA {

    public void drawMaze(Canvas canvas) {
        // compute maze AND render it on canvas...
    }
}
person James_D    schedule 06.04.2018
comment
Спасибо за вашу помощь, это очень ценится. Возможно, я еще не совсем понимаю JavaFX, но ваше объяснение мне очень помогло. Я пойду тем путем, который вы рекомендуете! - person Casper Hansen; 07.04.2018