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

标签 javafx-2

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

并且调用 updateProgress(0) 不会使表盘重新出现。

我尝试使用 ProgressIndicator 作为全局变量来显式重置它

mainController.progressDial.setProgress(0);

但是失败了

java.lang.RuntimeException:无法设置绑定(bind)值。 在 javafx.beans.property.DoublePropertyBase.set(DoublePropertyBase.java:159)

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

最佳答案

我的回答中有一些文字,因为从您的问题中我不太清楚您的实例出了什么问题。希望答案中的解释或示例代码有用。

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.

你有点误会了。您已将指标的进度绑定(bind)到任务的进度。任务已完成,进度为 1。现在,如果您想将同一指标重新用于另一项任务或让它衡量其他事情的进度,您必须首先停止它衡量原始任务的进度。要取消与原始任务的进度指示器的关联,unbind这是进步。一旦进度指示器的进度不再绑定(bind)到原始任务的进度,您就可以自由地将指示器设置为您想要的任何值,或将其绑定(bind)到其他内容。

类似地,您一次只能将进度指示器的进度绑定(bind)到一件事(除非您双向绑定(bind)指示器,这不能用于任务进度,因为任务进度是只读的并且双向绑定(bind)到无论如何,多个任务进度值都是不正确的,因为每个任务都处于不同的进度点)。

make the dial reappear.

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

一个建议

您只能运行一次任务,因此在完成后,如果它已经取得了一些进展,则将其进度设置回零没有多大意义。

属性类型

A progress indicator's progress property是一个普通的 DoubleProperty,而不是 ReadOnlyDoubleProperty,因此它可以直接设置(只要它没有绑定(bind)到另一个值)。

A task's progress property这是只读的,必须通过 updateProgress 更改.任务的进度属性可能已设置为只读,以便可以通过 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);
  }
}

其他问题的更新

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.

使用 Creating multiple parallel tasks 中定义的机制并行运行事件.为您的整体奥运会进度创建一个进度指示器,并使用低级绑定(bind) api 将其绑定(bind)到所有任务的进度总和的进度。

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

关于javafx-2 - 如何重置 JavaFX2 中任务之间的进度指示器?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16368793/

相关文章:

JavaFX 和装有 Windows XP 的旧 PC

java - 如何将场景生成器中创建的场景加载到JavaFX应用程序主类中?

java - javafx 中每个 .css 文件和行的 "Unknown property"警告

TableView 中的 JavaFX 单元格颜色

JavaFX-2:场景和 Pane 有什么区别

java - CellFactory 与 CellValueFactory

java - 让一个球在另一个球内弹跳

text - Javafx 2.0 阿拉伯文文本顺序不正确

java - fxmlLoader.getController() 导致空指针异常?

eclipse-rcp - 如何将 JavaFX 嵌入到 eclipse rcp View 中