0

I have a GridPane with an image on it. I would like the image to change its position (go to a different grid child node) every 2 seconds. However when I ran the program using another thread I get: Not on FX application thread error. Any help is appreciated!

I have tried using Platform.runLater as well but it still doesn't work. Also by reading through different posts I learned that GridPane is not thread safe. Is there a way to bypass that?

public class BoardController implements Initializable {
    @FXML
    private GridPane board;
    @FXML
    private Text position;

    String pikachuPos = "";
    String pokeballPos = "";
    int i = 0;
    int j = 0;
    boolean selectPikachu = true;
    int count = 0;

    int pikachuRow = 0;
    int pikachuCol = 0;
    int pokeballRow = 0;
    int pokeballCol = 0;

    ImageView pikachu = new ImageView(getClass().getResource("pikachu.png").toExternalForm());
    ImageView pokeball = new ImageView(getClass().getResource("pokeball.png").toExternalForm());

    /**
     * Initializes the controller class.
     */
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        pikachu.setFitWidth(30);
        pikachu.setFitHeight(30);
        pokeball.setFitWidth(30);
        pokeball.setFitHeight(30);

        position.setVisible(true);
        if (count == 2) {
            System.out.println("2");
        }
        boolean pikachu = true;

        Button[][] buttons = new Button[10][10];
        Button b;
        for (i = 0; i < 10; i++) {
            for (j = 0; j < 10; j++) {
                b = new Button(j + " " + i);
                buttons[i][j] = b;
                buttons[i][j].setOnAction((event) -> {
                    if (this.selectPikachu) {
                        Button button = (Button) event.getSource();
                        this.pikachuPos = button.getText();
                        this.selectPikachu = false;
                        this.position.setText("Choose Pokeball position");
                    } else {
                        Button button = (Button) event.getSource();
                        this.pokeballPos = button.getText();
                        this.position.setText("");
                        try {
                            this.play();
                        } catch (InterruptedException ex) {
                            Logger.getLogger(BoardController.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }

                });

                board.add(buttons[i][j], i, j);
                GridPane.setHalignment(buttons[i][j], HPos.CENTER);
            }
        }
    }

    public void play() throws InterruptedException {

        Node node = board.getChildren().get(0);
        board.getChildren().clear();
        board.getChildren().add(0, node);

        String[] pikachuDetails = pikachuPos.split(" ");
        String[] pokeballDetails = pokeballPos.split(" ");

        pikachuRow = Integer.parseInt(pikachuDetails[1]);
        pikachuCol = Integer.parseInt(pikachuDetails[0]);

        pokeballRow = Integer.parseInt(pokeballDetails[1]);
        pokeballCol = Integer.parseInt(pokeballDetails[0]);

        ImageView pokeball = new ImageView(getClass().getResource("pokeball.png").toExternalForm());
        pokeball.setFitWidth(30);
        pokeball.setFitHeight(30);

        ImageView pikachu = new ImageView(getClass().getResource("pikachu.png").toExternalForm());
        pikachu.setFitWidth(30);
        pikachu.setFitHeight(30);

        GridPane.setHalignment(pokeball, HPos.CENTER);
        board.add(pokeball, pokeballRow, pokeballCol);

        GridPane.setHalignment(pikachu, HPos.CENTER);
        board.add(pikachu, pikachuRow, pikachuCol);

        startTask();

    }

    public void startTask() {
        // Create a Runnable
        Runnable task = new Runnable() {
            public void run() {
                runTask();
            }
        };

        // Run the task in a background thread
        Thread backgroundThread = new Thread(task);
        // Terminate the running thread if the application exits
        backgroundThread.setDaemon(true);
        // Start the thread
        backgroundThread.start();
    }

    public void runTask() {
        for (int i = 1; i <= 10; i++) {
            try {
                board.add(pikachu, 5, 5);
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void setPikachu(int r, int c) {
        board.add(pikachu, r, c);
        GridPane.setHalignment(pikachu, HPos.CENTER);
    }

}
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
manzk
  • 586
  • 1
  • 4
  • 11
  • Possible duplicate of https://stackoverflow.com/questions/21083945/how-to-avoid-not-on-fx-application-thread-currentthread-javafx-application-th – quant Nov 13 '18 at 00:25
  • Explain what "still doesn't work" means when you use `Platform.runLater`. Are you still getting exceptions? – Slaw Nov 13 '18 at 01:42

1 Answers1

0

I learned that GridPane is not thread safe.

There is no such thing as thread-safe when it comes to JavaFX scene nodes. They are expected to be modified only from JavaFX Application Thread.

I have tried using Platform.runLater as well but it still doesn't work.

You have to place it at the correct part too. If you did Platform.runLater(this::startTask); then it would do nothing.

You should use Platform.runLater(() -> board.....) to modify the scene graph from non JavaFX Application Thread.

Lastly, you really should not use board.add(). Adding a control multiple times is going to throw an IllegalArgumentException saying that there is a duplicate children.

So this is what you should do:

Platform.runLater(() -> board.setConstraints(pikachu, 5, 5));

I am not sure why you are always setting it to row 5 and column 5 though.

Jai
  • 8,165
  • 2
  • 21
  • 52
  • Hi, thank you for letting me know. Yeah sorry for the confusion I was setting it always to 5,5 only as part of my debugging. – manzk Nov 14 '18 at 02:00