Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
470 views
in Technique[技术] by (71.8m points)

javafx 2 - How to reset progress indicator between tasks in JavaFX2?

I have one progress indicator on my main screen UI that is shared by various tabs and services. Each TabController has its own instance of Service. In my MainController class, for each tab I have bound each Service's progress property to the ProgressIndicator.

@FXML
Region veil;
@FXML
ProgressIndicator progressDial;

  progressDial.progressProperty().bind(tabController.commandService.progressProperty());
    veil.visibleProperty().bind(tabController.commandService.runningProperty());
    progressDial.visibleProperty().bind(tabController.commandService.runningProperty());
    tabController.commandService.messageProperty().addListener(new ChangeListener<String>() {
        @Override
        public void changed(ObservableValue<? extends String> ov, String t, String newValue) {
            addCommentary(newValue);
        }
    });

However I see that after the first service uses it, the progress dial does not appear for the execution of subsequent services or tasks. I am wondering if I am misusing the ProgressIndicator since each Service probably runs concurrently. I am guessing that the progress wasn't reset after the first finished. How do I reset it? The progress property is read only.

ReadOnlyDoubleProperty progressProperty() Gets the ReadOnlyDoubleProperty representing the progress.

And calling updateProgress(0) does nothing to make the dial reappear.

I tried to explicitly reset it using the ProgressIndicator as a global

mainController.progressDial.setProgress(0);

but this failed

java.lang.RuntimeException: A bound value cannot be set. at javafx.beans.property.DoublePropertyBase.set(DoublePropertyBase.java:159)

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.

// ext

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...