I'm using a timeline in JavaFX to do a countdown of a Label
:
timeline.setCycleCount(6);
timeline.play();
And I want to return a value after the timeline has finished:
return true;
However, it seems that the value is getting returned immediately and the timeline runs parallel. How can I wait until the timeline has finished its countdown and then return the value without blocking the timeline?
EDIT:
To make it more clear, I already tried:
new Thread(() -> {
timeline.play();
}).start();
while(!finished){ // finished is set to true, when the countdown is <=0
}
return true;
(This solution doesn't update the countdown.)
EDIT 2:
Here is a minimal, complete and verifiable example:
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class CountdownTest extends Application {
private Label CountdownLabel;
private int Ctime;
@Override
public void start(Stage primaryStage) {
CountdownLabel=new Label(Ctime+"");
StackPane root = new StackPane();
root.getChildren().add(CountdownLabel);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Countdown Test");
primaryStage.setScene(scene);
primaryStage.show();
Ctime=5;
if(myCountdown()){
CountdownLabel.setText("COUNTDOWN FINISHED");
}
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
public boolean myCountdown(){
final Timeline timeline = new Timeline(
new KeyFrame(
Duration.millis(1000),
event -> {
CountdownLabel.setText(Ctime+"");
Ctime--;
}
)
);
timeline.setCycleCount(6);
timeline.play();
return true;
}
}
You can see that it first shows "COUNTDOWN FINISHED" and counts down to 0 instead of starting with the countdown and counting down to "COUNTDOWN FINISHED".
As a Timeline
inherits from Animation
, you can use setOnFinished
to define an action to occur at the end of the timeline.
timeline.setCycleCount(6);
timeline.play();
timeline.setOnFinished(event -> countdownLabel.setText("COUNTDOWN FINISHED"));
If you really want to wait till the timeline finished, you can use a CountDownLatch
or a Semaphore
along with setOnFinished
. Something like the following should work:
CountDownLatch latch = new CountDownLatch(1);
timeline.setCycleCount(6);
timeline.setOnFinished(event -> latch.countDown());
timeline.play();
latch.await();
return true;
You are trying to wait in the one thread for the result of the work in the another thread. That is what synchronisation was created for! E.g. java.util.concurrent.Semaphore
:
public boolean waitForTimeline() {
Semaphore semaphore = new Semaphore(0);
System.out.println("starting timeline");
Timeline t = new Timeline();
t.getKeyFrames().add(new KeyFrame(Duration.seconds(2)));
t.setOnFinished((e)-> {
System.out.println("releasing semaphore");
semaphore.release();
});
t.play();
System.out.println("waiting for timeline to end");
try {
semaphore.acquire();
} catch (InterruptedException ex) {
ex.printStackTrace();
return false;
}
return true;
}
But note, that you can't run this method on "JavaFX Application Thread" as it will block UI updates. Run it on a separate thread:
new Thread(()-> {
System.out.println("returned from timeline with " + waitForTimeline());
}).start();
or, better, instead create a listener with the logic you do after return
and call that listener from t.setOnFinished()
. For your example, it will be:
public void myCountdown(Runnable onSuccess){
//...
timeline.setOnFinished((e)-> {
onSuccess.run();
});
}
and corresponding call:
myCountdown(()->{
CountdownLabel.setText("COUNTDOWN FINISHED");
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With