Javafx Not on fx application thread when using timer


Question

I'm using this

import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;



public class Main extends Application {

public static void main(String[] args) {
    launch(args);
}

@Override
public void start(Stage primaryStage) {
    Group root = new Group();
    Scene scene = new Scene(root, 600, 400);
    primaryStage.setScene(scene);
    Circle circle = new Circle(300,200,50, Color.BLACK);
    primaryStage.setTitle("Circle");
    primaryStage.setResizable(false);
    root.getChildren().add(circle);
    moveCircle(circle, scene);
    primaryStage.show();
}
public int random(int min, int max) {
    return new Random().nextInt((max - min) + min);
}

public int random(int max) {
    return random(0, max);
}

public void moveCircle(Circle circle, Scene scene) {
    Platform.runLater(() -> {
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {

            @Override
            public void run() {
                circle.setCenterX(random((int) scene.getX()));
                circle.setCenterY(random((int) scene.getY()));

            }
        }, 1000, 1000);
    });
}

But this:

public void moveCircle(Circle circle, Scene scene) {
    Platform.runLater(() -> {
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {

            @Override
            public void run() {
                circle.setCenterX(random((int) scene.getX()));
                circle.setCenterY(random((int) scene.getY()));

            }
        }, 1000, 1000);
    });
}

Gives me this error:

Exception in thread "Timer-0" java.lang.IllegalStateException: Not on FX application thread; currentThread = Timer-0
    at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:204)
    at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:364)
    at javafx.scene.Scene.addToDirtyList(Scene.java:485)
    at javafx.scene.Node.addToSceneDirtyList(Node.java:424)
    at javafx.scene.Node.impl_markDirty(Node.java:415)
    at javafx.scene.shape.Shape.impl_markDirty(Shape.java:942)
    at javafx.scene.shape.Circle$1.invalidated(Circle.java:136)
    at javafx.beans.property.DoublePropertyBase.markInvalid(DoublePropertyBase.java:112)
    at javafx.beans.property.DoublePropertyBase.set(DoublePropertyBase.java:146)
    at javafx.scene.shape.Circle.setCenterX(Circle.java:122)
    at Main$2.run(Main.java:48)
    at java.util.TimerThread.mainLoop(Timer.java:555)
    at java.util.TimerThread.run(Timer.java:505)

And i really don't see what's wrong

1
8
1/15/2016 1:02:52 PM

Accepted Answer

It may be because you misunderstood how Platform.runLater() works..

The correct code snippet would be:

public void moveCircle(Circle circle, Scene scene) {
    Timer timer = new Timer();
    timer.scheduleAtFixedRate(new TimerTask() {
        @Override
        public void run() {
            Platform.runLater(() -> {
                circle.setCenterX(random((int) scene.getX()));
                circle.setCenterY(random((int) scene.getY()));
            });
        }
    }, 1000, 1000);
}

But:

I would strongly recommend you to not use Timer but TimeLine instead! It is part of the JavaFX API and you do not have to do these Platform.runLater() calls. This is just quickly hacked together, but you get the idea:

public void moveCircle(Circle circle, Scene scene) {
    Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1), ev -> {
        circle.setCenterX(random((int) scene.getX()));
        circle.setCenterY(random((int) scene.getY()));
    }));
    timeline.setCycleCount(Animation.INDEFINITE);
    timeline.play();
}
34
2/4/2015 7:26:00 AM

Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon