cdr_chakotay
cdr_chakotay

Reputation: 66

Running same Task in JavaFX after completion again

What I am trying:

I try to build a countdown timer for home workout, which runs down the same interval twice and then gives you an extra interval break. After that it should start over again with the 3 intervals.

How far did I get?:

Currently I am successfully running the first interval from thirty seconds down to zero. My problem is, that I can not determine whether the JavaFX Task is completed or not. More precisely I cannot start over with it again WITHOUT creating several self overwriting process (e.g. with a for loop).

Code and GUI:

This is my Controller.class for handling my FXML file:

import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.Slider;


public class Controller {

private int time;

@FXML
private Label counterLabel;

private static StringProperty displayTime = new SimpleStringProperty("30");

@FXML
private Button startstop;

@FXML
private ProgressIndicator progress;

private static DoubleProperty prog = new SimpleDoubleProperty(0.0);

@FXML
private Slider Pausendauer; // pause length

@FXML
private Slider Trainingsdauer; //interval length

@FXML
private Slider Repeats;

public void initialize() {
    progress.setProgress(0);
    counterLabel.textProperty().bind(displayTime);
    progress.progressProperty().bind(prog);
}


public void click(ActionEvent e) throws InterruptedException {
    //event for clicking start button

    //Task for Counting (Should be repeatable after running through
        time = (int)Trainingsdauer.getValue();
            Task<String> count = new TaskTimer(time);
            displayTime.bind(count.valueProperty());
            prog.bind(count.progressProperty());
            count.run();
    }
}

And that is my Task (Timer) class:

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javafx.concurrent.Task;


public class TaskTimer extends Task<String>{

    private int time;
    double fraction;
    double prog;
    public static void main(String[] args) {
    }

    public TaskTimer(int sec) {
        this.time = sec;
        this.fraction = 1.0 / time; //claculating for closng circle
        this.prog = 0.0;
    }

    protected String call() throws Exception {
        // TODO Auto-generated method stub
        //runs scheduled without delay
        ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
            scheduler.scheduleAtFixedRate(new Runnable(){
            public void run() {
                time = time - 1; //Decrement of time
                prog = prog + fraction; //increase progress
                updateProgress(prog, 1);

                if(time >= 10) { // Makes 01 from 1 and so on
                updateValue(time+"");
                }else {
                updateValue("0"+time);
                }
                System.out.println(time+"");


                if (time <= 0) {
                    updateProgress(1, 1);
                    scheduler.shutdown();
                }
            }

            }, 1, 1, TimeUnit.SECONDS);

            return time+"";

        }
    }

And that's the application at work (the sad one it iteration it's capable of):

GUI Design to describe function

What can I try next? Have tried a whole bunch of things right now. Most of my "solutions" ended up with self overwriting twins and or a freezing UI.

Upvotes: 2

Views: 962

Answers (2)

c0der
c0der

Reputation: 18792

You can use the Task.setOnSucceeded method to construct and invoke a new Task as demonstrated in the following mre:

import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class RestartProgressIndicator extends Application {

    private static int COUNT_DOWN = 10;
    @Override
    public void start(Stage stage) {

        Work work = new Work();
        Button button = new Button("Go");
        button.setDefaultButton(true);
        button.setOnAction(e -> {
            work.work(COUNT_DOWN);
            button.setDisable(true);
        });

        Pane root = new BorderPane(null,work.getPane(),null, button,null);
        root.setPadding(new Insets(20.));
        Scene scene = new Scene(root, Color.WHITE);
        stage.setScene(scene);
        stage.centerOnScreen();
        stage.show();
    }

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

class Work{

    private TaskTimer count;
    private final ProgressIndicator pi;
    private final Pane pane;
    private int counter = 0;

    Work(){
        pi = new ProgressIndicator();
        pi.setMinWidth(150);  pi.setMinHeight(150);
        pane = new Pane(pi);
    }

    void work(int seconds){

        count = new TaskTimer(seconds);
        pi.progressProperty().bind(count.progressProperty());

        count.setOnSucceeded(e->{
            pi.progressProperty().unbind();
            if(isRestarart()) {
                work(seconds);
            }
        });

        Thread th = new Thread(count);
        th.setDaemon(true);
        th.start();
    }

    private boolean isRestarart() {
        // TODO apply restart logic
        return ++counter < 3;
    }

    Pane getPane() {
        return pane;
    }
}

class TaskTimer extends Task<String>{

    private final int seconds;
    double fraction, prog;

    public TaskTimer(int seconds) {
        this.seconds = seconds;
        fraction = 1.0 / seconds;
        prog = 0.0;
    }

    @Override
    protected String call() throws Exception {

        int time = seconds;
        while(true){
            time = time - 1; //Decrement of time
            prog = prog + fraction; //increase progress
            updateProgress(prog, 1);

            if(time >= 10) { // Makes 01 from 1 and so on
                updateValue(time+"");
            }else {
                updateValue("0"+time);
            }

            TimeUnit.SECONDS.sleep(1);

            if (time <= 0) {
                updateProgress(1, 1);
                break;
            }
        };

        return time+"";
    }
}

Upvotes: 3

Alexiy
Alexiy

Reputation: 2030

You have to add ExecutorService#awaitTermination at the end of the call() of your Task, otherwise it will skip to return immediately:

class TaskTimer extends Task<String>{

    private int time;
    double fraction;
    double prog;

    public TaskTimer(int sec) {
        this.time = sec;
        this.fraction = 1.0 / time;
        this.prog = 0.0;
        //to show when it is (re)started
        setOnSucceeded(event -> System.out.println("Restart"));
    }

    @Override
    protected String call() throws Exception {
        ScheduledExecutorService executorService=Executors.newSingleThreadScheduledExecutor();
        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                time = time - 1;
                prog = prog + fraction;
                TaskTimer.this.updateProgress(prog, 1);

                if (time >= 10) {
                    TaskTimer.this.updateValue(time + "");
                } else {
                    TaskTimer.this.updateValue("0" + time);
                }
                System.out.println(time + "");

                if (time <= 0) {
                    TaskTimer.this.updateProgress(1, 1);
                    executorService.shutdown();
                }
            }
        },1,1,TimeUnit.SECONDS);

        //this here
        executorService.awaitTermination(10,TimeUnit.SECONDS);

        return time+"";
    }
}

And to make it repeatable you can utilize a javafx.concurrent.Service:

int repetitions;
@Override
public void start(Stage primaryStage) throws Exception {

    Service<String> service=new Service<>() {
        @Override
        protected Task<String> createTask() {
            return new TaskTimer(5);
        }
    };

    //repeat 3 times
    service.setOnSucceeded(event -> {
        if(repetitions < 2)
        {
            service.restart();
        }
        repetitions++;
    });
    service.start();
    primaryStage.show();
}

Upvotes: 0

Related Questions