Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return a value after JavaFX Timeline has finished

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".

like image 479
user3776738 Avatar asked Aug 27 '18 10:08

user3776738


3 Answers

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"));
like image 138
FThompson Avatar answered Oct 23 '22 06:10

FThompson


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;
like image 2
Dakshinamurthy Karra Avatar answered Oct 23 '22 05:10

Dakshinamurthy Karra


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");
});
like image 2
Sergey Grinev Avatar answered Oct 23 '22 06:10

Sergey Grinev