这个问题的其他答案似乎没有使用专门为此目的制作的 Task 类。确实使用它的答案,我觉得在某些地方没有遵循最佳实践。
这是我对 Yggdrasil 答案的整理(我认为这是迄今为止最好的)。具体变化如下:
- 当 Task 已经具有带有 updateProgress 方法的进度属性时,不要使用 Platform.runLater 从 Task 的调用方法中更新进度)
- 覆盖 Task 的成功和失败方法,而不是添加状态监听器
- 尝试将状态封装在任务中,例如它正在计算的正方形的索引
- 修复了导致其他所有矩形计算失败的状态问题
- 添加了随机失败以显示失败任务的 UI 更新
- 修复了基于服务的任务中的进度更新
- 对布局代码的小幅调整
import java.util.Random;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.TilePane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class AsyncProgress extends Application {
static final Random rng = new Random();
private Group root;
private ProgressBar progress;
private TilePane loadPane;
private ProgressIndicator[] indicators = new ProgressIndicator[9];
private Label loading[] = new Label[9];
private Color[] colors = {Color.BLACK, Color.BLUE, Color.CRIMSON, Color.DARKCYAN, Color.FORESTGREEN, Color.GOLD, Color.HOTPINK, Color.INDIGO, Color.KHAKI};
private int counter = 0;
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
//creating Layout
root = new Group();
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.setResizable(false);
progress = new ProgressBar();
Label load = new Label("loading things...");
VBox waitingPane = new VBox(10, load, progress);
waitingPane.setPrefSize(400, 400);
waitingPane.setAlignment(Pos.CENTER);
root.getChildren().add(waitingPane);
//Task for computing the Panels:
Task<Void> panelTask = new Task<Void>() {
@Override
protected Void call() throws Exception {
updateProgress(0, 0);
for (int i = 0; i < 20; i++) {
sleep(rng.nextInt(500));
updateProgress(i * 0.05, 1);
}
return null;
}
@Override
protected void succeeded() {
loadPanels();
}
};
progress.progressProperty().bind(panelTask.progressProperty());
primaryStage.show();
var t = new Thread(panelTask);
t.setDaemon(true);
t.start();
}
private void loadPanels() {
progress.progressProperty().unbind();
root.getChildren().set(0, createLoadPane());
final Service<Rectangle> recBuilderService = new Service<Rectangle>() {
@Override
protected Task<Rectangle> createTask() {
return new Task<Rectangle>() {
private int squareIndex = counter++;
@Override
protected Rectangle call() {
updateMessage("loading rectangle . . .");
updateProgress(0, 10);
for (int i = 0; i < 10; i++) {
sleep(100);
if (rng.nextDouble() > 0.95) {
throw new RuntimeException("Rectangle failed to load.");
}
updateProgress(i + 1, 10);
}
updateMessage("Finish!");
return new Rectangle((380) / 3, (380) / 3, colors[squareIndex]);
}
@Override
protected void succeeded() {
setRectangle(squareIndex, getValue());
}
@Override
protected void failed() {
failRectangle(squareIndex, getException());
}
};
}
};
recBuilderService.stateProperty().addListener((observableValue, oldState, newState) -> {
switch (newState) {
case SUCCEEDED:
case FAILED:
if (counter <= 8) {
nextPane(recBuilderService);
}
default:
break;
}
});
// kickstart the panel building
nextPane(recBuilderService);
}
private void nextPane(Service<Rectangle> recBuilder) {
loading[counter].textProperty().bind(recBuilder.messageProperty());
var prog = indicators[counter];
prog.visibleProperty().bind(prog.progressProperty().isNotEqualTo(ProgressBar.INDETERMINATE_PROGRESS, 0));
prog.progressProperty().bind(recBuilder.progressProperty());
recBuilder.restart();
}
private void failRectangle(int index, Throwable reason) {
var tt = new Tooltip(reason.getMessage());
var msg = loading[index];
msg.setTooltip(tt);
msg.textProperty().unbind();
msg.setText("Failed!");
var ind = indicators[index];
ind.progressProperty().unbind();
ind.setTooltip(tt);
}
private void setRectangle(int index, Rectangle rec) {
indicators[index].progressProperty().unbind();
var lab = loading[index];
lab.textProperty().unbind();
loadPane.getChildren().set(index, rec);
}
private Node createLoadPane() {
loadPane = new TilePane(5, 5);
loadPane.setPrefColumns(3);
loadPane.setPadding(new Insets(5));
for (int i = 0; i < 9; i++) {
Rectangle background = new Rectangle(380 / 3, 380 / 3, Color.WHITE);
indicators[i] = new ProgressIndicator();
indicators[i].setPrefSize(50, 50);
indicators[i].setMaxSize(50, 50);
loading[i] = new Label();
loading[i].setTranslateY(35);
StackPane waitingPane = new StackPane(background, indicators[i], loading[i]);
waitingPane.setAlignment(Pos.CENTER);
loadPane.getChildren().add(waitingPane);
}
return loadPane;
}
static void sleep(long ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}