I'm trying to find an efficient way to influence the shape and the content of the JavaFX GUI elements, such as simple Pane
, with use of multithreading. Let's say I have a simple Pane
, on which I display filled Circle
s at the given itervals of time, and I want to have the posibility to answer to them, e.g. by hitting the corresponding key. So far, for this purpose, I tried to use class with the implementation of Runnable
interface and creation of classic Thread
object in it, in order to add and/or remove elements from the external JavaFX Pane
, which was passed to that "thread class" in its constructor parameter from the main Application
class. Say, both classes, 1) application and 2) thread class, looks like this:
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class ClassApplication extends Application {
private Pane pane;
public Parent createContent() {
/* layout */
BorderPane layout = new BorderPane();
/* layout -> center */
pane = new Pane();
pane.setMinWidth(250);
pane.setMaxWidth(250);
pane.setMinHeight(250);
pane.setMaxHeight(250);
pane.setStyle("-fx-background-color: #000000;");
/* layout -> center -> pane */
Circle circle = new Circle(125, 125, 10, Color.WHITE);
/* add items to the layout */
pane.getChildren().add(circle);
layout.setCenter(pane);
return layout;
}
@Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setWidth(300);
stage.setHeight(300);
stage.show();
/* initialize custom Thread */
ClassThread thread = new ClassThread(pane);
thread.execute();
}
public static void main(String args[]) {
launch(args);
}
}
...and the "thread class"
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
public class ClassThread implements Runnable {
private Thread t;
private Pane pane;
ClassThread(Pane p) {
this.pane = p;
t = new Thread(this, "Painter");
}
@Override
public void run() {
try {
Thread.sleep(2000);
Circle circle = new Circle(50, 50, 10, Color.RED);
pane.getChildren().clear();
pane.getChildren().add(circle);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
public void execute() {
t.start();
}
}
However, such a solution, where in Swing application could be possible, in JavaFX is impossible, and what's more, is the reason of the following exception:
Exception in thread "Painter" java.lang.IllegalStateException: Not on FX application thread; currentThread = Painter
at com.sun.javafx.tk.Toolkit.checkFxUserThread(Unknown Source)
at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(Unknown Source)
at javafx.scene.Parent$2.onProposedChange(Unknown Source)
at com.sun.javafx.collections.VetoableListDecorator.clear(Unknown Source)
at ClassThread.run(ClassThread.java:21)
at java.lang.Thread.run(Unknown Source)
...where the line "21" is: pane.getChildren().clear();
I've concluded, that "there is a problem with influencing the main JavaFX thread from the level of another thread". But in this case, how can I change the JavaFX GUI elements shape and content dynamically, if I can't (tbh more accurately to say will be "I don't know how") bind togheter few threads?
UPDATE : 2014/08/07, 03:42
After reading given answers I tried to implement given solutions in code, in order to display 10 custom Circle
s on different locations with specified time intervals between each display:
/* in ClassApplication body */
@Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setWidth(300);
stage.setHeight(300);
stage.show();
Timeline timeline = new Timeline();
for (int i = 0; i < 10; i++) {
Random r = new Random();
int random = r.nextInt(200) + 25;
KeyFrame f = new KeyFrame(Duration.millis((i + 1) * 1000),
new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent ae) {
pane.getChildren().add(new Circle(
random, random, 10, Color.RED));
}
});
timeline.getKeyFrames().add(f);
}
timeline.setCycleCount(1);
timeline.play();
}
The solution above works just fine. Thank you very much.