javajavafxnullpointerexceptioninvocationtargetexception

JavaFX InvocationTargetException when pressing button in Pane


I am simply trying to draw a line from another class in my JavaFX project (as a start for some homework). But I am running into this InvocationTargetException along with a NullPointerException exception.

"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

I have tried this answer from James_D, but it still won't run after trying his solution.

Main:

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>

Controller:

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

    kruAlg.drawMaze();

    // kruAlg.solveMaze();

}

KrushkalA class:

    @FXML
    Canvas canvas;
    GraphicsContext gc;


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

Perhaps this is what I'm trying to do; to run a method in the KrushkalA class from the controller, which draws a line or just does-something within the canvas when pressing a button.

Is that the wrong way to do it? I can run the code that causes the exception in my Controller class, but not from any other class? Example code will be much appreciated.


Solution

  • The reason your code doesn't work is that you are expecting the canvas to be injected from the FXML into your KrushkalA instance. This simply isn't how this works: @FXML-injection is done by the FXMLLoader when the FXML file is loaded, and any @FXML-annotated fields in the controller instance are initialized with corresponding elements in the FXML file with matching fx:id. Even if you add a fx:id attribute to the canvas defined in the FXML, there's no way that the FXMLLoader can possibly know about the KrushkalA object (which isn't even created at that point, though it wouldn't help if it were).

    The whole premise here seems flawed anyway. It's generally a bad idea to allow references to your UI elements to escape from the controller. Suppose at some point in the future you decide you don't want to draw this with a Canvas any more (e.g. you might decide to add Lines, or some other node, to a Pane). In this case, you really should only need to modify the FXML and controller class; if you have allowed your KrushkalA class access to the canvas, then you would need to change that too, and you'd need to look at whether that class passed the canvas reference to anyone else. Maintaining this gets much more difficult if you break encapsulation in this way.

    If your KrushkalA class is really just implementing the algorithm, it shouldn't have anything to do with the UI. I think here you are creating a maze (or, more abstractly, a spanning tree for a graph), so your drawMaze() method should just compute the maze and return the result. At it's core, a maze is just a collection of boundaries (or edges) between cells (nodes), so I think I would define a Boundary class with some simple representation of which boundary it is (maybe int x, int y, and boolean horizontal fields, or something else convenient); then you would have:

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

    Then back in your controller, you would do:

    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.
        }
    }
    

    And, of course, add fx:id="canvas" to the <Canvas> element in the FXML.

    This approach properly manages separation of concerns and abides by the single responsibility principle: the controller only has responsibility for updating the UI, and doesn't concern itself with how the algorithm is implemented, and the KrushkalA class only has responsibility for implementing the algorithm, and doesn't care how the UI is rendered (or even if there is a UI...).


    If you really wanted the algorithm class to render the maze on the canvas, which I strongly do not recommend, then of course you could just pass the canvas to it:

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

    and

    public class KrushkalA {
    
        public void drawMaze(Canvas canvas) {
            // compute maze AND render it on canvas...
        }
    }