How to reset progress indicator between tasks in JavaFX2?

There is a bit of writing in my answer because it’s not exactly clear to me from your question what is going wrong with your instance. Hopefully either the explanation or the sample code in the answer is useful.

I could be mistaken, but I think this is a fault in the JavaFX UI controls design. Updating progress to 0 should reset the progress Indicator.

You are slightly mistaken. You have bound the progress of the indicator to the progress of a task. The task is completed and progress is 1. Now if you want to re-use the same indicator for another task or make it measure the progress of something else, you have to first stop it from measuring the progress of the original task. To disassociate the progress indicator for the original task, unbind it’s progress. Once the progress indicator’s progress is no longer bound to the original task’s progress, you are free to set the indicator to whatever value you want, or bind it to something else.

Similarly, you can only bind the progress indicator’s progress to one thing at a time (unless you bi-directionally bind the indicator, which you can’t do with task progress because task progress is read only and bi-directionally binding to multiple task progress values would be incorrect anyway as each task would be at a different progress point).

make the dial reappear.

I’m not sure from your description why the dial would disappear in the first place so that it would need to reappear. Normally, when a progress indicator’s progress reaches 1, it still stays visible reporting fully completed progress, it doesn’t automatically disappear. You are likely setting the visibility of the indicator to false or modifying it’s opacity to zero. Both of those properties have nothing to do with the actual progress measured by the indicator. Or maybe you are removing the indicator from the displayed scene. If you are modifying visibility and setting the indicator to invisible after a task is completed and you want to subsequently see it again to measure the progress of another task, then you will need to make sure it is in the scene, with opacity > 0 and visibility set to true.

A suggestion

You can only run a task once, so after it is done, it doesn’t make a lot of sense to set it’s progress back to zero if it had already made some progress.

Property types

A progress indicator’s progress property is a plain DoubleProperty, not a ReadOnlyDoubleProperty, so it is directly settable (as long as it is not bound to another value).

A task’s progress property which is read only and must be changed via updateProgress. The task’s progress property was likely made read only so that updates to it can be ensured to be threadsafe by special code in the updateProgress routine.


Sample Code

Consider the following code which (I believe) accomplishes the intent of what you are trying to do. The code simulates running a triathlon where each stage (swim, bike, run) of the triathlon is a separate task. While a triathlon is being run, a progress indicator shows the progress of each stage of the triathlon. When the triathlon completes the progress indicator fades away until a new triathlon is started. Sorry the sample is so lengthy, I found it hard to come up with something more concise.

triatholonracemonitor

import javafx.animation.FadeTransition;
import javafx.application.Application;
import javafx.beans.*;
import javafx.beans.property.*;
import javafx.beans.value.*;
import javafx.concurrent.Task;
import javafx.event.*;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import javafx.util.Duration;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Triathlon extends Application {

  private final Random random = new Random();
  private final ExecutorService exec = Executors.newSingleThreadExecutor();

  @Override public void start(Stage stage) throws Exception {
    final TaskMonitor taskMonitor = new TaskMonitor();

    final ProgressIndicator progressIndicator = new ProgressIndicator();
    progressIndicator.progressProperty().bind(
        taskMonitor.currentTaskProgressProperty()
    );

    final Label currentRaceStage = new Label();
    currentRaceStage.textProperty().bind(
        taskMonitor.currentTaskNameProperty()
    );

    createMainLayout(
        stage,
        createStartRaceButton(
            exec,
            taskMonitor
        ),
        createRaceProgressView(
            taskMonitor,
            progressIndicator,
            currentRaceStage
        )
    );
  }

  @Override public void stop() throws Exception {
    exec.shutdownNow();
  }

  private Button createStartRaceButton(final ExecutorService exec, final TaskMonitor taskMonitor) {
    final Button startButton = new Button("Start Race");
    startButton.disableProperty().bind(taskMonitor.idleProperty().not());
    startButton.setOnAction(new EventHandler<ActionEvent>() {
      @Override
      public void handle(ActionEvent actionEvent) {
        runRace(exec, taskMonitor);
      }
    });
    return startButton;
  }

  private HBox createRaceProgressView(final TaskMonitor taskMonitor, ProgressIndicator progressIndicator, Label currentRaceStage) {
    final HBox raceProgress = new HBox(10);
    raceProgress.getChildren().setAll(
      currentRaceStage,
      progressIndicator
    );
    raceProgress.setOpacity(0);
    raceProgress.setAlignment(Pos.CENTER);

    final FadeTransition fade = new FadeTransition(Duration.seconds(0.75), raceProgress);
    fade.setToValue(0);

    taskMonitor.idleProperty().addListener(new InvalidationListener() {
      @Override
      public void invalidated(Observable observable) {
        if (taskMonitor.idleProperty().get()) {
          fade.playFromStart();
        } else {
          fade.stop();
          raceProgress.setOpacity(1);
        }
      }
    });

    return raceProgress;
  }

  private void createMainLayout(Stage stage, Button startButton, HBox raceProgress) {
    final VBox layout = new VBox(10);
    layout.getChildren().setAll(
      raceProgress,
      startButton
    );
    layout.setAlignment(Pos.CENTER);
    layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 10px;");
    stage.setScene(new Scene(layout, 200, 130));
    stage.show();
  }


  private void runRace(ExecutorService exec, TaskMonitor taskMonitor) {
    StageTask swimTask = new StageTask("Swim", 30,   40);
    StageTask bikeTask = new StageTask("Bike", 210, 230);
    StageTask runTask  = new StageTask("Run",  120, 140);

    taskMonitor.monitor(swimTask, bikeTask, runTask);

    exec.execute(swimTask);
    exec.execute(bikeTask);
    exec.execute(runTask);
  }

  class TaskMonitor {
    final private ReadOnlyObjectWrapper<StageTask> currentTask = new ReadOnlyObjectWrapper<>();
    final private ReadOnlyStringWrapper currentTaskName        = new ReadOnlyStringWrapper();
    final private ReadOnlyDoubleWrapper currentTaskProgress    = new ReadOnlyDoubleWrapper();
    final private ReadOnlyBooleanWrapper idle                  = new ReadOnlyBooleanWrapper(true);

    public void monitor(final StageTask task) {
      task.stateProperty().addListener(new ChangeListener<Task.State>() {
        @Override
        public void changed(ObservableValue<? extends Task.State> observableValue, Task.State oldState, Task.State state) {
          switch (state) {
            case RUNNING:
              currentTask.set(task);
              currentTaskProgress.unbind();
              currentTaskProgress.set(task.progressProperty().get());
              currentTaskProgress.bind(task.progressProperty());
              currentTaskName.set(task.nameProperty().get());
              idle.set(false);
              break;

            case SUCCEEDED:
            case CANCELLED:
            case FAILED:
              task.stateProperty().removeListener(this);
              idle.set(true);
              break;
          }
        }
      });
    }

    public void monitor(final StageTask... tasks) {
      for (StageTask task: tasks) {
        monitor(task);
      }
    }

    public ReadOnlyObjectProperty<StageTask> currentTaskProperty() {
      return currentTask.getReadOnlyProperty();
    }

    public ReadOnlyStringProperty currentTaskNameProperty() {
      return currentTaskName.getReadOnlyProperty();
    }

    public ReadOnlyDoubleProperty currentTaskProgressProperty() {
      return currentTaskProgress.getReadOnlyProperty();
    }

    public ReadOnlyBooleanProperty idleProperty() {
      return idle.getReadOnlyProperty();
    }
  }

  class StageTask extends Task<Duration> {
    final private ReadOnlyStringWrapper name;
    final private int minMinutesElapsed;
    final private int maxMinutesElapsed;

    public StageTask(String name, int minMinutesElapsed, int maxMinutesElapsed) {
      this.name = new ReadOnlyStringWrapper(name);
      this.minMinutesElapsed = minMinutesElapsed;
      this.maxMinutesElapsed = maxMinutesElapsed;
    }

    @Override protected Duration call() throws Exception {
      Duration duration = timeInRange(
        minMinutesElapsed, maxMinutesElapsed
      );

      for (int i = 0; i < 25; i++) {
        updateProgress(i, 25);
        Thread.sleep((int) (duration.toMinutes()));
      }
      updateProgress(25, 25);

      return duration;
    }

    private Duration timeInRange(int min, int max) {
      return Duration.minutes(
        random.nextDouble() * (max - min) + min
      );
    }

    public ReadOnlyStringProperty nameProperty() {
      return name.getReadOnlyProperty();
    }
  }

  public static void main(String[] args) {
    Application.launch(Triathlon.class);
  }
}

Update for Additional Question

Instead of being a triathlon, suppose each stage was instead, an independent event (like in the Olympics). So swim, bike, run etc. are instances of SportService. They execute concurrently. On the stadium electronic scoreboard is a progress indicator dial that is shared by all SportServices swim, bike, run etc. It gives me the approximate general progress – though I realize that is vague but is a summary of how everything is progressing without seeing the details of each event.

Run the events in parallel using the mechanism defined in Creating multiple parallel tasks. Create a single progress indicator for your overall olympics progress and bind it to the progress of the sum of progress for all tasks using the low level binding api.

ObservableList<Service> services = FXCollections.observableArrayList();

. . .  add services to list.

// extract the progress property for each of the added services.
final ReadOnlyDoubleProperty[] taskProgressList = new ReadOnlyDoubleProperty[services.size()];
for (int i = 0; i < taskProgressList.length; i++) {
  taskProgressList[i] = services.get(i).progressProperty();
}

// calculate the average progress of all services.
DoubleBinding overallProgress =  Bindings.createDoubleBinding(new Callable<Double>() {
  @Override public Double call() throws Exception {
    double value = 0;

    for (int i = 0; i < taskProgressList.length; i++) {
      value += taskProgressList[i].get();
    }

    value /= taskProgressList.length;

    return value;
  }
}, taskProgressList);

// bind the overall progress to our indicator
ProgressIndicator overallProgressIndicator = new ProgressIndicator();
overallProgressIndicator.progressProperty().bind(overallProgress);

Here is a another sample which demonstrates use of the overallProgress DoubleBinding.

progress summary

import java.io.*;
import java.net.URL;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.*;
import javafx.event.EventHandler;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;

public class FirstLineSequentialVsParallelService extends Application {
  private static final String[] URLs = {
    "http://www.google.com", 
    "http://www.yahoo.com", 
    "http://www.microsoft.com", 
    "http://www.oracle.com" 
  };

  private ExecutorService sequentialFirstLineExecutor;
  private ExecutorService parallelFirstLineExecutor;

  @Override public void init() throws Exception {
    sequentialFirstLineExecutor = Executors.newFixedThreadPool(
      1, 
      new FirstLineThreadFactory("sequential")
    );  

    parallelFirstLineExecutor = Executors.newFixedThreadPool(
      URLs.length, 
      new FirstLineThreadFactory("parallel")
    );  
  }

  @Override
  public void stop() throws Exception {
    parallelFirstLineExecutor.shutdown();
    parallelFirstLineExecutor.awaitTermination(3, TimeUnit.SECONDS);

    sequentialFirstLineExecutor.shutdown();
    sequentialFirstLineExecutor.awaitTermination(3, TimeUnit.SECONDS);
  }

  public static void main(String[] args) { launch(args); }
  @Override public void start(Stage stage) throws Exception {
    final VBox messages = new VBox();
    messages.setStyle("-fx-background-color: cornsilk; -fx-padding: 10;");

    messages.getChildren().addAll(
      new Label("Parallel Execution"), 
      new Label("------------------")
    );
    DoubleBinding parallelProgress = fetchFirstLines(messages, parallelFirstLineExecutor);
    ProgressMonitoredLabel parallelProgressSummary = new ProgressMonitoredLabel("Parallel Execution Summary");
    parallelProgressSummary.progress.progressProperty().bind(parallelProgress);
    messages.getChildren().add(parallelProgressSummary);

    messages.getChildren().addAll(
      new Label("Sequential Execution"), 
      new Label("--------------------")
    );
    DoubleBinding  sequentialProgress = fetchFirstLines(messages, sequentialFirstLineExecutor);
    ProgressMonitoredLabel sequentialProgressSummary = new ProgressMonitoredLabel("Sequential Execution Summary");
    sequentialProgressSummary.progress.progressProperty().bind(sequentialProgress);
    messages.getChildren().add(sequentialProgressSummary);

    messages.setStyle("-fx-font-family: monospace;");

    stage.setScene(new Scene(messages, 600, 650));
    stage.show();
  }

  private DoubleBinding fetchFirstLines(final VBox monitoredLabels, ExecutorService executorService) {
    ObservableList<Service> services = FXCollections.observableArrayList();
    for (final String url: URLs) {
      final FirstLineService service = new FirstLineService();
      service.setExecutor(executorService);
      service.setUrl(url);

      final ProgressMonitoredLabel monitoredLabel = new ProgressMonitoredLabel(url);
      monitoredLabels.getChildren().add(monitoredLabel);
      monitoredLabel.progress.progressProperty().bind(service.progressProperty());

      service.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
        @Override public void handle(WorkerStateEvent t) {
          monitoredLabel.addStrings(
            service.getMessage(),
            service.getValue()
          );
        }
      });
      service.start();

      services.add(service);
    }

    final ReadOnlyDoubleProperty[] taskProgressList = new ReadOnlyDoubleProperty[services.size()];
    for (int i = 0; i < taskProgressList.length; i++) {
      taskProgressList[i] = services.get(i).progressProperty();
    }

    return Bindings.createDoubleBinding(new Callable<Double>() {
      @Override public Double call() throws Exception {
        double value = 0;

        for (int i = 0; i < taskProgressList.length; i++) {
          value += taskProgressList[i].get();
        }

        value /= taskProgressList.length;

        return value;
      }
    }, taskProgressList);
  }

  public class ProgressMonitoredLabel extends HBox {
    final ProgressBar progress;
    final VBox labels;

    public ProgressMonitoredLabel(String initialString) {
      super(20);

      progress = new ProgressBar();
      labels   = new VBox();
      labels.getChildren().addAll(new Label(initialString), new Label());

      progress.setPrefWidth(100);
      progress.setMinWidth(ProgressBar.USE_PREF_SIZE);
      HBox.setHgrow(labels, Priority.ALWAYS);
      setMinHeight(60);

      getChildren().addAll(progress, labels);
    }

    public void addStrings(String... strings) {
      for (String string: strings) {
        labels.getChildren().add(
          labels.getChildren().size() - 1,
          new Label(string)
        );
      }
    }
  }

  public static class FirstLineService extends Service<String> {
    private StringProperty url = new SimpleStringProperty(this, "url");
    public final void setUrl(String value) { url.set(value); }
    public final String getUrl() { return url.get(); }
    public final StringProperty urlProperty() { return url; }
    protected Task createTask() {
      final String _url = getUrl();
      return new Task<String>() {
        { updateProgress(0, 100); }
        protected String call() throws Exception {
          updateMessage("Called on thread: " + Thread.currentThread().getName());
          URL u = new URL(_url);
          BufferedReader in = new BufferedReader(
                  new InputStreamReader(u.openStream()));
          String result = in.readLine();
          in.close();

          // pause just so that it really takes some time to run the task 
          // so that parallel execution behaviour can be observed.
          for (int i = 0; i < 100; i++) {
            updateProgress(i, 100);
            Thread.sleep(50); 
          }

          return result;
        }
     };
    }
  }

  static class FirstLineThreadFactory implements ThreadFactory {
    static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final String type;

    public FirstLineThreadFactory(String type) {
      this.type = type;
    }

    @Override public Thread newThread(Runnable runnable) {
      Thread thread = new Thread(runnable, "LineService-" + poolNumber.getAndIncrement() + "-thread-" + type);
      thread.setDaemon(true);

      return thread;
    }
  }  
}

Leave a Comment