Reputation: 66
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.
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).
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
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
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