如何在JavaFX2中的任务之间重置进度指示器?

时间:2013-05-03 23:07:56

标签: javafx-2

我的主屏幕UI上有一个进度指示器,由各种选项卡和服务共享。 每个TabController都有自己的Service实例。 在我的MainController类中,对于每个选项卡,我将每个Service的progress属性绑定到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);
        }
    });

但是我发现在第一个服务使用它之后,进度拨号不会出现,以执行后续服务或任务。 我想知道我是否滥用ProgressIndicator,因为每个服务可能同时运行。 我猜测第一次完成后进度没有重置。我该如何重置? progress property是只读的。

  

ReadOnlyDoubleProperty progressProperty()获取   表示进度的ReadOnlyDoubleProperty。

调用updateProgress(0)不会使拨号重新出现。

我尝试使用ProgressIndicator作为全局

显式重置它

mainController.progressDial.setProgress(0);

但是失败了

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

我可能会弄错,但我认为这是JavaFX UI控件设计中的一个错误。将进度更新为0应重置进度指示器。

1 个答案:

答案 0 :(得分:13)

在我的回答中有一些写作,因为我的问题并不完全清楚你的实例出了什么问题。希望答案中的解释或示例代码都是有用的。

  

我可能会弄错,但我认为这是JavaFX UI控件设计中的一个错误。将进度更新为0应重置进度指示器。

你有点误会。您已将指标的进度限制为任务的进度。任务完成,进度为1.现在,如果要为同一个任务重新使用相同的指标或使其测量其他任务的进度,则必须先停止测量原始任务的进度。要取消原始任务的进度指示器,unbind它的进度。一旦进度指示器的进度不再与原始任务的进度绑定,您就可以自由地将指标设置为您想要的任何值,或将其绑定到其他值。

同样,你只能将进度指示器的进度一次绑定到一个东西(除非你双向绑定指标,这是你无法完成任务进度的,因为任务进度是只读的并且双向绑定到无论如何,多个任务进度值都是不正确的,因为每个任务都处于不同的进度点。)

  

让表盘重新出现。

我不确定你的描述为什么表盘会首先消失,因此需要重新出现。通常,当进度指示器的进度达到1时,它仍然可见报告完全完成的进度,它不会自动消失。您可能将指标的可见性设置为false或将其不透明度修改为零。这两个属性都与指标测量的实际进度无关。或者您可能正在从显示的场景中删除指示器。如果要修改可见性并将指示器设置为在任务完成后不可见,并且您希望随后再次看到它以测量另一个任务的进度,那么您需要确保它在场景中,不透明度> 0并且可见性设置为true。

建议

你只能运行一次任务,所以在它完成之后,如果它已经取得了一些进展,那么将它的进度设置回零是没有多大意义的。

物业类型

progress indicator's progress property是普通DoubleProperty,而不是ReadOnlyDoubleProperty,因此可以直接设置(只要它不绑定到其他值)。

task's progress property是只读的,必须通过updateProgress进行更改。任务的progress属性可能只读,以便通过updateProgress例程中的特殊代码确保对它的更新是线程安全的。


示例代码

考虑下面的代码(我相信)完成了你想要做的事情的意图。该代码模拟了三项全能运动,其中铁人三项的每个阶段(游泳,自行车,跑步)都是一项单独的任务。在进行铁人三项运动时,进度指示器显示了铁人三项每个阶段的进展。当铁人三项完成时,进度指示器逐渐消失,直到新的铁人三项开始。对不起,样本太长了,我发现很难想出更简洁的东西。

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);
  }
}

更新附加问题

  

而不是铁人三项,假设每个阶段都是一个独立的事件(如奥运会)。所以游泳,骑自行车,跑步等都是SportService的例子。它们同时执行。在体育场上,电子记分牌是一个进度指示器表盘,由所有SportServices游泳,自行车,跑步等共享。它给了我近似的一般进展 - 虽然我意识到这是模糊的但是总结了一切都在进展而没有看到细节每个事件。

使用Creating multiple parallel tasks中定义的机制并行运行事件。为您的整体奥运会进度创建单一进度指示器,并使用低级别绑定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);

这是另一个演示使用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;
    }
  }  
}