DaveK
DaveK

Reputation: 586

JavaFX - Adding Image to a GridPane Manually

I have the following setup

enter image description here

The three tic tac toe boards are gridpanes in JavaFx.

I am trying a very simple approach of adding an ImageView to the grid node in order to indicate if it is an x or o.

The code I am doing that with is.

private void handleNewGameAction(ActionEvent event) throws IOException {

        FXMLLoader fxmlloader = new FXMLLoader(getClass().getResource("FXMLDocument.fxml"));

        GridPane pane = (GridPane)fxmlloader.getNamespace().get("topGrid");

        Image image = new Image("x.png");
        pane.add(new ImageView(image), 0, 0);

    }

The error I am getting is

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.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    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.control.MenuItem.fire(MenuItem.java:462)
    at com.sun.javafx.scene.control.skin.ContextMenuContent$MenuItemContainer.doSelect(ContextMenuContent.java:1405)
    at com.sun.javafx.scene.control.skin.ContextMenuContent$MenuItemContainer.lambda$createChildren$343(ContextMenuContent.java:1358)
    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.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:394)
    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$353(GlassViewEventHandler.java:432)
    at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:431)
    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$147(WinApplication.java:177)
    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)
    ... 43 more
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:620)
    at pkg3dtictactoe.FXMLDocumentController.handleNewGameAction(FXMLDocumentController.java:40)
    ... 53 more
Caused by: java.lang.IllegalArgumentException: Invalid URL or resource not found
    at javafx.scene.image.Image.validateUrl(Image.java:1110)
    ... 55 more

I am really at a loss and I have been looking at Stack overflow and seeing answers that look exactly like I have written, but I still get the error above.

Upvotes: 1

Views: 1992

Answers (1)

Slaw
Slaw

Reputation: 46265

First, to answer the immediate problem, the ultimate cause of the error is given by the stack trace:

Caused by: java.lang.IllegalArgumentException: Invalid URL or resource not found
    at javafx.scene.image.Image.validateUrl(Image.java:1110)
    ... 55 more

This means that the URL ("x.png") you give to the constructor of Image is invalid. Either you have used the wrong path or the resource is not where you think it is. You mention in the comments that you have your image file in the same directory as your .java files. If you're using Maven or Gradle the default location for resources is src/main/resources (and src/main/java for *.java files). If this is a pure NetBeans JavaFX project (which may use Ant?) I don't know where the resources are supposed to go. See if there is a directory designated for resources and, if there is, move all resource files there.

There are a couple other things wrong with your code:

private void handleNewGameAction(ActionEvent event) throws IOException {

    FXMLLoader fxmlloader = new FXMLLoader(getClass().getResource("FXMLDocument.fxml"));

    GridPane pane = (GridPane)fxmlloader.getNamespace().get("topGrid");

    Image image = new Image("x.png");
    pane.add(new ImageView(image), 0, 0);

}

The first issue deals with fxmlloader.getNamespace().get("topGrid"). You are calling this on a newly created FXMLLoader and you haven't called load() yet. The result of this call will always be null because the FXMLLoader hasn't actually instantiated anything yet.

The second, and in my opinion larger, issue deals with the fact you are creating a new GridPane every time this method is invoked. This new GridPane is never added to any Scene and therefore the GUI will never be updated. And to be clear, the GridPane you are retrieving in the method is not the same one that is already being displayed. And when the method exits the GridPane falls out of scope and is garbage collected.

Since you are creating a game board you should only ever need to load the board once (or maybe once per game). After that you use the loaded board to add images to. You should be using a controller class that's linked with the FXML file.

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;

public class Controller {

  @FXML 
  private GridPane topGrid;

  @FXML
  private void handleAction(ActionEvent event) {
    topGrid.add(new ImageView(/*image*/), 0, 0);
    event.consume();
  }

}

To link a controller to an FXML file you would use either the fx:controller attribute in the file or you would use FXMLLoader.setController(Object) before calling the instance load() method. To retrieve a controller you can use FXMLLoader.getController() after calling load.

To learn more about FXML in JavaFX, see Introduction to FXML.

Upvotes: 2

Related Questions