JavaFX Application Thread to Task Communication - java

I've been learning about JavaFX's Tasks and using these to communicate with the Application thread using Platform.runLater or the task's updateValue method etc. However, my Task needs to know when a user presses a button on the GUI as this could change the value needed to be returned by the Task's updateValue method. How do I go about doing this? I know how to respond to button press events on single threaded applications but am not sure how do deal with it in a thread-safe manner.
Update:
This is what I have so far, is this a sensible way of implementing the button event?
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.PixelFormat;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.control.Button;
import java.nio.IntBuffer;
public class TaskExample extends Application {
private Canvas canvas;
private PixelWriter pixel_writer;
#Override
public void start(Stage primaryStage) throws Exception {
canvas = new Canvas(256, 256);
pixel_writer = canvas.getGraphicsContext2D().getPixelWriter();
MyTask task = new MyTask();
task.valueProperty().addListener((c) -> {
if(task.getValue() != null) {
update(task.getValue());
}
});
Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();
Button button = new Button("Button 1");
// On the button click event it calls the eventFired() method
button.setOnAction((event) -> {
task.eventFired();
});
Pane pane = new VBox();
pane.getChildren().addAll(canvas, button);
primaryStage.setScene(new Scene(pane));
primaryStage.show();
}
public void update(IntBuffer data) {
pixel_writer.setPixels(
0,
0,
256,
256,
PixelFormat.getIntArgbInstance(),
data,
256
);
}
public static void main(String[] args) {
launch(args);
}
class MyTask extends Task<IntBuffer> {
public void eventFired() {
System.out.println("Event fired");
}
public void update(IntBuffer data) {
updateValue(data);
}
#Override
protected IntBuffer call() throws InterruptedException {
while(true) {
for (int i=0; i<3; i++) {
Thread.sleep(1000);
IntBuffer data = IntBuffer.allocate(256*256);
for(int j=0; j<256*256; j++) {
switch(i) {
case 0: data.put(0xFF0000FF); break;
case 1: data.put(0xFF00FF00); break;
case 2: data.put(0xFFFF0000); break;
}
}
data.rewind();
update(data);
}
}
}
}
}

What I would do here is to think about ways to refactor what you are doing to avoid communication between two different threads. For example, instead of thinking of what you are doing as one long-running task that updates the UI as it progresses, think of it as a series of individual tasks that each update the UI when they complete. The ScheduledService class provides the machinery to manage these tasks and communicate between them and the FX Application Thread in a clean and safe way:
import java.nio.IntBuffer;
import java.util.Arrays;
import javafx.application.Application;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.control.Button;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.PixelWriter;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class TaskExample extends Application {
private Canvas canvas;
private PixelWriter pixel_writer;
#Override
public void start(Stage primaryStage) throws Exception {
canvas = new Canvas(256, 256);
pixel_writer = canvas.getGraphicsContext2D().getPixelWriter();
MyService service = new MyService();
service.setPeriod(Duration.seconds(1));
service.valueProperty().addListener((ols, oldData, newData) -> {
if(newData != null) {
update(newData);
}
});
service.start();
Button button = new Button("Button 1");
Pane pane = new VBox();
pane.getChildren().addAll(canvas, button);
primaryStage.setScene(new Scene(pane));
primaryStage.show();
}
public void update(IntBuffer data) {
pixel_writer.setPixels(
0,
0,
256,
256,
PixelFormat.getIntArgbInstance(),
data,
256
);
}
public static void main(String[] args) {
launch(args);
}
class MyService extends ScheduledService<IntBuffer> {
// both instance variables accessed only on FX Application Thread:
private final int[] colors = {0xFF0000FF, 0xFF00FF00, 0xFFFF0000} ;
private int count = -1 ;
#Override
protected Task<IntBuffer> createTask() {
// invoked on FX Application Thread
count = (count + 1) % colors.length ;
return new MyTask(colors[count]);
}
}
class MyTask extends Task<IntBuffer> {
private final int color ;
MyTask(int color) {
// invoked on FX Application Thread:
this.color = color ;
}
#Override
protected IntBuffer call() {
// invoked on background thread:
IntBuffer data = IntBuffer.allocate(256*256);
int[] a = new int[256*256];
Arrays.fill(a, color);
data.put(a, 0, a.length);
data.rewind();
return data ;
}
}
}
You haven't been very specific about how the UI is supposed to interact with the background thread, but if you wanted to change the behavior of the service when the button is pressed, you would now be changing the behavior of the createTask method, which is invoked on the FX Application Thread, instead of changing the behavior of a method already running on a different thread. This avoids any "low-level" concerns about synchronization.
For example:
class MyService extends ScheduledService<IntBuffer> {
// all instance variables accessed only on FX Application Thread:
private final int[][] colors = {
{0xFF0000FF, 0xFF00FF00, 0xFFFF0000},
{0xFF00FFFF, 0xFFFF00FF, 0xFFFFFF00}
};
private int count = -1 ;
private int scheme = 0 ;
#Override
protected Task<IntBuffer> createTask() {
// invoked on FX Application Thread
count = (count + 1) % colors[scheme].length ;
return new MyTask(colors[scheme][count]);
}
public void changeScheme() {
// invoked on FX Application Thread
scheme = (scheme + 1) % colors.length ;
}
}
and then just
button.setOnAction(e -> service.changeScheme());
Adding a call to service.restart(); here will force the change to happen as soon as possible:
button.setOnAction(e -> {
service.changeScheme();
service.restart();
});
There is pretty much always a way to refactor your code to take advantage of the library classes like this to avoid low-level communication between threads.

Related

Non-blocking signaling between threads

I have a JavaFX app that runs two threads at startup. One is the UI thread that must not be blocked. The other is a thread that prepares a large table (it takes about 20 seconds). I want to signal the UI thread when the second thread is done, so it can change the color of a rectangle from red to green. I have tried solutions using the synchronized keyword, but they all caused the UI thread to be blocked.
I used the following resources to obtain the below code.
Concurrency in JavaFX
Execute task in background in JavaFX
The below app simply displays a red rectangle which, after five seconds, turns to green. Explanations after the code.
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.concurrent.Worker.State;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class JfxTask0 extends Application {
private Task<Void> task;
#Override
public void init() throws Exception {
task = new Task<Void>() {
#Override
protected Void call() throws Exception {
try {
Thread.sleep(5000L);
}
catch (InterruptedException xInterrupted) {
if (isCancelled()) {
System.out.println("CANCELLED!");
}
}
return null;
}
};
}
#Override
public void start(Stage primaryStage) throws Exception {
Rectangle rect = new Rectangle(25.0d, 25.0d, 50.0d, 50.0d);
rect.setFill(Color.RED);
task.stateProperty().addListener(new ChangeListener<Worker.State>() {
#Override
public void changed(ObservableValue<? extends State> workerStateProperty,
Worker.State oldValue,
Worker.State newValue) {
if (newValue == Worker.State.SUCCEEDED) {
rect.setFill(Color.GREEN);
}
}
});
new Thread(task).start();
Group root = new Group();
ObservableList<Node> children = root.getChildren();
children.add(rect);
Scene scene = new Scene(root, 100.0D, 100.0D);
primaryStage.setScene(scene);
primaryStage.setTitle("Task");
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Method init() is declared in class javafx.application.Application. It is executed before method start() and, as its name suggests, is used to initialize the JavaFX application. In this method I create the background task. The background task merely sleeps for five seconds.
In method start() I create the red rectangle and then launch the background task but before launching the task, I register a listener with one of the task's properties. This property will be set to a particular value once the task completes.
After the task is launched, I build the rest of the GUI and display it.
Once the task terminates, then listener is invoked and it sets the rectangle color to green.
You can use a handler for this problem.
there is example
Add this in your main activity and create handler.
Handler h = new Handler(){
#Override public void handleMessage(Message msg){
switch(msg.what){
case 1:
// what you want when complete
break;
default:
break;
}
}
}
MyThread thread = new MyThread(new Messenger(h));
thread.start();
Now add this in your thread file.
public class MyThread{
Messenger m;
public MyThread(Messenger m){
this.m = m;
}
public void run(){
super.run();
// your codes
//
//when your task complete
Message msg = Message.obtain();
msg.what = 1;
msg.obj = "";
try{
m.send(msg);
}catch(IOException e){
e.printStackTrace();
}
}
}

Trying to stop a single thread out of multiple running at the same time in java

So im trying to stop a single thread when I have multiple threads running, here is the code im using to initialise the threads. Basically I have multiple textFields in javafx, and when a button is clicked on the screen, it fills the textFields, one by one, with an incrementing timer. Now I also have a button for each of the textfields to clear it, but the problem is when I clear it, because the thread is still running, the timer vanishes for a second and comes back because of the line 'orderTimes.get(boxNo).setText(minute + second);' in the code.
Now what I've tried is creating a list of threads and I've tried implementing it below but it doesn't work, this is so I can call each individual thread if its button to clear has been clicked.
Does anyone know how I can close/stop only one single thread out of multiple that are running? If more info is needed just let me know, thanks.
public static void createIncrementingTimer(int boxNo, List<TextField> orderTimes) {
minutesList.set(boxNo, 0);
secondsList.set(boxNo, 0);
state = true;
new Thread(threadList.get(boxNo)) {
int currentMinutes = 0;
int currentSeconds = 0;
public void run() {
for (;;) {
if (state = true) {
try {
sleep(1000);
if (secondsList.get(boxNo) > 59) {
secondsList.set(boxNo, 0);
currentSeconds = 0;
minutesList.set(boxNo, currentMinutes + 1);
currentMinutes++;
}
if (secondsList.get(boxNo) < 10) {
second = ":0" + Integer.toString(secondsList.get(boxNo));
} else {
second = ":" + Integer.toString(secondsList.get(boxNo));
}
secondsList.set(boxNo, currentSeconds + 1);
currentSeconds++;
if (minutesList.get(boxNo) < 10) {
minute = "0" + Integer.toString(minutesList.get(boxNo));
} else {
minute = Integer.toString(minutesList.get(boxNo));
}
orderTimes.get(boxNo).setText(minute + second);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
};
threadList.get(boxNo).start();
}
The code I'm using to clear the textfields is below, with orderTimes being the list of textFields that I'm trying to clear.
public static void eraseBox(int clickedButtonNumber, List<TextArea> orderContentsList, List<TextField> tableNumbers, List<TextField> orderNumbers, List<TextField> orderTimes) {
orderContentsList.get(clickedButtonNumber).setText(null);
tableNumbers.get(clickedButtonNumber).clear();
orderNumbers.get(clickedButtonNumber).clear();
orderTimes.get(clickedButtonNumber).clear();
}
I would suggest you try to avoid Threads. The Animation API is designed to make doing work that would normally be done in a Thread easier. In this example, the IncrementingTimer class consists of two Labels and three Buttons. The Labels are used to show the time. The Buttons are used to control the Timeline. The Timeline is used to increment the Labels value each second or every sixty seconds. I have added three IncrementingTimers to the app.
Main
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
* JavaFX App
*/
public class App extends Application {
#Override
public void start(Stage stage) {
var scene = new Scene(new VBox(new IncrementingTimer(), new IncrementingTimer(), new IncrementingTimer()), 640, 480);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
IncrementingTimer
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.util.Duration;
/**
*
* #author blj0011
*/
final public class IncrementingTimer extends HBox
{
IntegerProperty secondsCounter = new SimpleIntegerProperty();//Keeps up with seconds
IntegerProperty minutesCounter = new SimpleIntegerProperty();//Keeps up with minutes
Label lblSeconds = new Label();//Displays the seconds
Label lblMinutes = new Label();//Displays the minutes
Label lblColon = new Label(":");//Display the colon between minutes and seconds
Button btnPlay = new Button("Play");//Plays the Timeline
Button btnStop = new Button("Stop");//Stops the Timeline
Button btnPause = new Button("Pause");//Pauses the Timeline
Timeline timeline;//Used to run code that changes the Labels. This Timeline runs every one second.
public IncrementingTimer()
{
lblSeconds.textProperty().bind(secondsCounter.asString("%02d"));//Binds the seconds label to the seconds counter. Sets the String to always show two digits. Exmaple 1 is shown as 01.
lblMinutes.textProperty().bind(minutesCounter.asString("%02d"));//Binds the minutes label to the minutes counter. Sets the String to always show two digits. Exmaple 1 is shown as 01.
getChildren().addAll(lblMinutes, lblColon, lblSeconds, btnPlay, btnStop, btnPause);
timeline = new Timeline(new KeyFrame(Duration.seconds(1), (event) -> {//Replace the one with .016 to speed this up for testing purposes.
secondsCounter.set(secondsCounter.get() + 1);
if (secondsCounter.get() == 60) {
secondsCounter.set(0);
minutesCounter.set(minutesCounter.get() + 1);
if (minutesCounter.get() == 60) {
minutesCounter.set(0);
}
}
}));
timeline.setCycleCount(Timeline.INDEFINITE);
btnPlay.setOnAction((event) -> {
timeline.play();
});
btnPause.setOnAction((event) -> {
timeline.pause();
});
btnStop.setOnAction((event) -> {
timeline.stop();
secondsCounter.set(0);
minutesCounter.set(0);
});
this.setAlignment(Pos.CENTER);
}
}
As recommended and demonstrated by Sedric, use JavaFx Animation tools for the counters.
The following one-file mre demonstrating implementation of counters using two different animation tools.
One uses PauseTransition and uses Timeline, each with its stop button.
(copy-paste the entire code into Timers.java and run)
import java.io.IOException;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.PauseTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Timers extends Application {
#Override public void start(final Stage stage) throws IOException {
VBox root = new VBox(new CounterPane(new TimeLineCounter()), new CounterPane(new PauseTransitionCounter()));
stage.setScene(new Scene(root));
stage.show();
}
public static void main(final String[] args) { launch(args); }
}
class CounterPane extends HBox{
private final Counter counter;
CounterPane(Counter counter) {
super(5);
this.counter = counter; //todo: check not null
Button stopBtn = new Button("Stop");
stopBtn.setOnAction(e->stop());
getChildren().addAll(stopBtn, counter);
}
void stop(){
counter.getAnimation().stop();
}
}
abstract class Counter extends Label {
protected int count = 0;
public Counter() {
setAlignment(Pos.CENTER); setPrefSize(25, 25);
count();
}
abstract void count();
abstract Animation getAnimation();
}
class TimeLineCounter extends Counter {
private Timeline timeline;
#Override
void count() {
timeline = new Timeline();
timeline.setCycleCount(Animation.INDEFINITE);
final KeyFrame keyFrame = new KeyFrame(
Duration.seconds(1),
event -> { setText(String.valueOf(count++) ); }
);
timeline.getKeyFrames().add(keyFrame);
timeline.play();
}
#Override
Animation getAnimation() {
return timeline;
}
}
class PauseTransitionCounter extends Counter {
private PauseTransition pauseTransition;
#Override
void count() {
pauseTransition = new PauseTransition(Duration.seconds(1));
pauseTransition.setOnFinished(event ->{
setText(String.valueOf(count++) );
pauseTransition.play();
});
pauseTransition.play();
}
#Override
Animation getAnimation() {
return pauseTransition;
}
}
The if(state=true) should rather be if(state==true) or just if(state), but in fact the for(;;) could do the entire thing as while(state), simply shutting down the thread when you set state=false.
Then, fully stopping the thread could happen as state=false;threadList.get(boxNo).join();, and you can clear the field only after that (since the thread will set it to something in the last step too).
With a simpler approach you could throw away the state, and revert to for(;;), with the twist of having the try-catch() around the loop, outside. This way you can use threadList.get(boxNo).interrupt();threadList.get(boxNo);.join(); to stop the thread, and on top of that it will be immediate, as the sleep() ends immediately when the thread is interrupted.

Need clarification on changing data in javafx application thread

I've been migrating a project of mine to JavaFX and started running into thread issues. I'll attach a short example. After much searching I managed to sort out the problem. I can't change the tableView data outside of the fx application thread. I switched my code over from using SwingWorker to a Task.
At first, that worked until I added a change listener to the table's observableList. I then received the error "Not on FX application thread;"
The error happened inside the onChanged method when I attempted to update a Label's value. I resolved this by wrapping it inside Platform.runLater().
I'm just confused as to why changing the label says it wasn't on the application thread. On what thread was this running? Also, am I adding rows to my table correctly by using a task? In my actual application, I could be adding 50k rows hence why the separate thread so as to not lock up the UI.
public class Temp extends Application{
private ObservableList<String> libraryList = FXCollections.observableArrayList();
public void start(Stage stage) {
Label statusLabel = new Label("stuff goes here");
TableView<String> table = new TableView<String>(libraryList);
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
TableColumn<String, String> col = new TableColumn<String, String>("Stuff");
col.setCellValueFactory(cellData -> new ReadOnlyStringWrapper(cellData.getValue()));
table.getColumns().add(col);
libraryList.addListener(new ListChangeListener<String>() {
public void onChanged(Change change) {
// Problem was caused by setting the label's text (prior to adding the runLater)
Platform.runLater(()->{
statusLabel.setText(libraryList.size()+" entries");
});
}
});
// dummy stuff
libraryList.add("foo");
libraryList.add("bar");
Button b = new Button("Press Me");
b.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent e) {
FileTask task = new FileTask();
new Thread(task).start();
}
});
BorderPane mainBody = new BorderPane();
mainBody.setTop(statusLabel);
mainBody.setCenter(table);
mainBody.setBottom(b);
Scene scene = new Scene(mainBody);
stage.setScene(scene);
stage.show();
}
class FileTask extends Task<Boolean>{
public FileTask(){
}
protected Boolean call() throws Exception{
Random rand = new Random();
for(int i = 0; i < 5; i++) {
String s = ""+rand.nextInt(Integer.MAX_VALUE);
libraryList.add(s);
}
return true;
}
}
public static void main(String[] args) {
Application.launch(args);
}
}
It's working as expected, you have the application thread and the task thread, they kind of look like this:
App ------\ ----------------------
Task \-label.setText() Exception
You can't do any UI work on anything but the App thread, so adding your RunLater does this:
App ----\ -------------/ RunLater(label.setText()) ----------
Task \-add to list/
which works well. There are a few ways to manage this based on what you want to do:
If you want to update the Table list within the Task, you can move the RunLater call to inside the task, rather than inside the handler, this way it will still get you back to the App thread. This way if you're actually on the app thread, there is no need to call RunLater within the handler.
App ---\ -----------------------/ label.setText() ----------
Task \-RunLater(add to list)/
Another option is to just use a Task> which will run on the other thread, and return the full list of strings that are going to be added. This is more likely what you want if you're making network calls in the task, get a list of items, then add them once they are all downloaded to the table.
App -----\ ------------------------------/ label.setText() ---/ add to table list-------
Task \-build list, update progress /- return final list /
Hopefully the formatting stays.
Consider encapsulating the information needed by the view in a separate class (typically referred to as model).
The view should respond to changes in the model by means of listener or binding.
You can use a thread or threads, to update the model:
import java.util.Random;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class Temp extends Application{
#Override
public void start(Stage stage) {
Model model = new Model();
Label statusLabel = new Label("stuff goes here");
TableView<String> table = new TableView<>(model.getLibraryList());
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
TableColumn<String, String> col = new TableColumn<>("Stuff");
col.setCellValueFactory(cellData -> new ReadOnlyStringWrapper(cellData.getValue()));
table.getColumns().add(col);
statusLabel.textProperty().bind(Bindings.concat(model.sizeProperty.asString(), " entries"));
// dummy stuff
model.add("foo"); model.add("bar");
Button b = new Button("Press Me");
b.setOnAction(e -> {
FileTask task = new FileTask(model);
new Thread(task).start();
});
BorderPane mainBody = new BorderPane();
mainBody.setTop(statusLabel);
mainBody.setCenter(table);
mainBody.setBottom(b);
Scene scene = new Scene(mainBody);
stage.setScene(scene);
stage.show();
}
class Model {
private final ObservableList<String> libraryList;
private final IntegerProperty sizeProperty;
Model(){
libraryList = FXCollections.observableArrayList();
sizeProperty = new SimpleIntegerProperty(0);
libraryList.addListener((ListChangeListener<String>) change -> {
Platform.runLater(()->sizeProperty.set(libraryList.size()));
});
}
//synchronize if you want to use multithread
void add(String string) {
Platform.runLater(()->sizeProperty.set(libraryList.add(string)));
}
ObservableList<String> getLibraryList() {
return libraryList;
}
IntegerProperty getSizeProperty() {
return sizeProperty;
}
}
class FileTask implements Runnable{
private final Model model;
public FileTask(Model model){
this.model = model;
}
#Override
public void run() {
Random rand = new Random();
for(int i = 0; i < 5; i++) {
String s = ""+rand.nextInt(Integer.MAX_VALUE);
model.add(s);
}
}
}
public static void main(String[] args) {
Application.launch(args);
}
}

How to get a scheduled event to interact with JavaFX GUI

I'm a beginner Java programmer trying to figure this out. I have a piece of code that does some calculation and updates a label in my JavaFX GUI. It runs every 100ms using a ScheduledExecutorService and a Runnable. The problem is it cannot update the Label of the GUI. I have spent yesterday looking for a way to do it and most of the topics seem to be solved with the use of Platform.runLater but even putting my code into the runLater runnable seems to still not work. Another thing I have found is using the Java concurrency framework, but I don't know how to use that for a repeating scheduled service like this. Here's how I wrote the code:
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
Runnable loop = new Runnable() {
public void run() {
Platform.runLater(new Runnable() {
#Override public void run() {
double result = calculation();
labelResult.setText("" + result);
}
});
}
};
executor.scheduleAtFixedRate(loop, 0, 100, TimeUnit.MILLISECONDS);
How could I do this?
EDIT:
I'm including a full example.
Main class:
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javafx.application.Application;
import javafx.application.Platform;
public class Main{
private static long value = 0;
private static Gui gui;
public static void main(String[] args){
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
Runnable loop = new Runnable() {
public void run() {
Platform.runLater(new Runnable() {
#Override public void run() {
calculate();
}
});
}
};
executor.scheduleAtFixedRate(loop, 0, 100, TimeUnit.MILLISECONDS);
Application.launch(Gui.class, args);
}
public static void calculate(){
double result = value++;
gui.setResult(result);
}
public static void setGui(Gui ref){
gui = ref;
}
}
Gui class:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class Gui extends Application{
private Stage window;
private Scene scene;
private HBox layout = new HBox();
private Label result = new Label("TEST");
#Override
public void start(Stage stage) throws Exception {
window = stage;
layout.getChildren().addAll(result);
Main.setGui(this);
scene = new Scene(layout, 1280, 720);
window.setTitle("Example");
window.setResizable(false);
window.setScene(scene);
window.show();
}
public void setResult(double res){
result.setText("" + res);
}
}
The overall structure of your application is wrong. The reason that your scheduled executor service is failing is that you attempt to start it before you launch the JavaFX application, and consequently your first call to Platform.runLater(...) happens before the FX toolkit has been started and before the FX Application Thread is running.
If you wrap the call to Platform.runLater() in a try block and catch the exception:
Runnable loop = new Runnable() {
public void run() {
try {
Platform.runLater(new Runnable() {
#Override
public void run() {
calculate();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
};
you will see the exception:
java.lang.IllegalStateException: Toolkit not initialized
at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:273)
at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:268)
at javafx.application.Platform.runLater(Platform.java:83)
at Main$1.run(Main.java:17)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
(Incidentally, handling the exception will also allow the executor to continue, so eventually it will "recover" as the toolkit will be started at some point. You may also see other exceptions, because, e.g. there are race conditions on the gui field: some iterations of the executor may get called before gui is initialized.)
You should think of the Application.start() method essentially as the entry point for the application. When you call launch() (or when it is called for you, which happens in most final deployment scenarios), the FX Toolkit is started, then an instance of the Application subclass is created, and start() is invoked on that instance on the FX Application Thread.
So the way to structure this is to drive it all from the start() method. Create an instance of your GUI class there, create an instance of the class that is running your scheduled executor, tie them together, and then just display the UI in the provided stage. Here's one possible example of this refactoring:
Main.java:
import javafx.application.Application;
import javafx.stage.Stage;
public class Main extends Application{
private Stage window;
#Override
public void start(Stage stage) throws Exception {
window = stage;
Gui gui = new Gui();
UpdateService service = new UpdateService(gui);
service.startService();
window.setTitle("Example");
window.setResizable(false);
window.setScene(gui.getScene());
window.show();
}
public static void main(String[] args) {
launch(args);
}
}
UpdateService.java:
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javafx.application.Platform;
public class UpdateService {
private long value = 0;
private final Gui gui;
public UpdateService(Gui gui) {
this.gui = gui;
}
public void startService() {
// create executor that uses daemon threads;
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1, runnable -> {
Thread t = new Thread(runnable);
t.setDaemon(true);
return t;
});
Runnable loop = new Runnable() {
public void run() {
Platform.runLater(new Runnable() {
#Override
public void run() {
calculate();
}
});
}
};
executor.scheduleAtFixedRate(loop, 0, 100, TimeUnit.MILLISECONDS);
}
public void calculate() {
double result = value++;
gui.setResult(result);
}
}
Gui.java:
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
public class Gui {
private Scene scene;
private HBox layout = new HBox();
private Label result = new Label("TEST");
public Gui() {
layout.getChildren().addAll(result);
scene = new Scene(layout, 1280, 720);
}
public Scene getScene() {
return scene ;
}
public void setResult(double res){
result.setText("" + res);
}
}
Finally, note that a cleaner way to get regularly-repeating functionality that runs on the FX Application Thread is to use the Animation API (as in JavaFX periodic background task):
public void startService() {
Timeline timeline = new Timeline(new KeyFrame(Duration.millis(100), e -> calculate()));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
}

How to force Java FX scene refresh?

I have an Java FX scene with a start button and several rectangles which represent the tiles of a map. I also have drawn a sphere which represents my explorer (it has to explore the map), but I am having difficulties with running the animation.
In my OnMouseClicked handler for the start button, I start an algorithm for exploring the map which changes the position of the sphere and the colors of the tiles which have been visited. The problem is that the scene won't update itself while the algorithm is running, so I only get to see how the final scene will look like (after the algorithm has stopped running). How can I force a scene update so I can see all the color changes sequentially?
Later edit:
import javafx.application.Application;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Test extends Application {
private static final double boxOuterSize = 50;
private static final double boxInnerSize = 48;
private static final double boxCornerRadius = 20;
private Stage applicationStage;
private Scene applicationScene;
private static double sceneWidth = 1024;
private static double sceneHeight = 800;
private static HBox container = new HBox();
private static Group root = new Group();
private Rectangle[] rectangles = new Rectangle[10];
#Override
public void start(Stage mainStage) throws Exception {
applicationStage = mainStage;
container.setSpacing(10);
container.setPadding(new Insets(10, 10, 10, 10));
try {
applicationScene = new Scene(container, sceneWidth, sceneHeight);
applicationScene.addEventHandler(EventType.ROOT,(EventHandler<? super Event>)this);
applicationScene.setFill(Color.WHITE);
} catch (Exception exception) {
System.out.println ("exception : "+exception.getMessage());
}
applicationStage.setTitle("HurtLockerRobot - Tema 3 IA");
applicationStage.getIcons().add(new Image("icon.png"));
applicationStage.setScene(applicationScene);
for(int i=0; i<10; i++) {
Rectangle r = new Rectangle();
r.setFill(Color.BLUE);
r.setX(i * boxOuterSize);
r.setY(0);
r.setWidth(boxInnerSize);
r.setHeight(boxInnerSize);
r.setArcHeight(boxCornerRadius);
r.setArcWidth(boxCornerRadius);
r.setSmooth(true);
rectangles[i] = r;
root.getChildren().add(rectangles[i]);
}
container.getChildren().add(root);
Button startButton = new Button("Start");
startButton.setOnMouseClicked(new EventHandler<Event>() {
#Override
public void handle(Event arg0) {
for(int i=0; i<10; i++) {
rectangles[i].setFill(Color.RED);
// TODO: some kind of scene refresh here
}
}
});
container.getChildren().add(startButton);
applicationStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Initially all the rectangles are blue. The behavior I want to obtain here is to see the rectangles changing colors sequentially. The problem is that I only get to see the end result (all the rectangles change their color at the same time).
This is an old question and it caught my eye since this is a very general issue faced by people new to JavaFX.
The problem that OP is facing is because he updates all the rectangles at once, without waiting.
OP can wait by either creating a new Thread, put the thread on sleep for an estimated seconds for every iteration of the loop and then update the color of the rectangle on JavaFX application thread by using Platform.runLater.
#Override
public void handle(Event arg0) {
new Thread(() -> {
for(int i=0; i<10; i++) {
try {
Thread.sleep(1000); // Wait for 1 sec before updating the color
} catch (InterruptedException e) {
e.printStackTrace();
}
int finalI = i;
Platform.runLater(() -> rectangles[finalI].setFill(Color.RED));// Update on JavaFX Application Thread
}
}).start();
The above snippet is more of a traditional way of doing things. If we want to use the "JavaFX" ways of doing things, we can achieve the same by using an Animation.
Below is a code snippet which will wait for x-seconds before changing the color of the rectangle. It doesn't need any extra thread since the wait is handled by PauseTransition applied for each rectangle.
startButton.setOnMouseClicked(new EventHandler<Event>() {
#Override
public void handle(Event arg0) {
for(int i=0; i<10; i++) {
PauseTransition pauseTransition = new PauseTransition(Duration.seconds(i));
int finalI = i;
pauseTransition.setOnFinished(event -> rectangles[finalI].setFill(Color.RED));
pauseTransition.play();
}
}
});
It creates a PauseTransition for each rectangle and depending on its index in the array rectangles, it waits for the same number of seconds before updating the color.
This is because of :
exception : Test cannot be cast to javafx.event.EventHandler
Well, I have no idea how Class cast exception came up.
Otherwise, to delay, you can use Thread.sleep().
UPDATE:
Its good to use AnimationTimer to create an animation, you don't need to refresh anything.
Here, I have done a short EG to show color rect using FillTransition.
CODE:
import javafx.animation.FillTransition;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class NewFXMain1 extends Application {
private static final double boxOuterSize = 50;
private static final double boxInnerSize = 48;
private static final double boxCornerRadius = 20;
private Rectangle rectangles = new Rectangle();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("rect");
Button btn = new Button();
StackPane root = new StackPane();
Rectangle r = new Rectangle();
r.setFill(Color.BLUE);
r.setX(2 * boxOuterSize);
r.setY(0);
r.setWidth(boxInnerSize);
r.setHeight(boxInnerSize);
r.setArcHeight(boxCornerRadius);
r.setArcWidth(boxCornerRadius);
r.setSmooth(true);
r.localToScene(boxOuterSize, boxOuterSize);
rectangles = r;
root.getChildren().add(rectangles);
btn.setText("display");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
FillTransition ft = new FillTransition(Duration.millis(3000), rectangles, Color.RED, Color.BLUE);
ft.setCycleCount(4);
ft.setAutoReverse(true);
ft.play();
}
});
root.getChildren().add(btn);
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
}
}

Categories

Resources