Other answer to this questions seem to not use the Task class which is specifically made for this purpose. The answer that does use it, I feel isn't following the best practices in some spots.
Here is my cleanup of Yggdrasil's answer (which I believe to be the best so far). Specific changes are:
- Don't use Platform.runLater to update progress from within a Task's call method when Task already has a
progress
property with an updateProgress
method)
- Override
succeeded
and failed
methods of Task rather than adding a state listener
- Try to encapsulate state in the Task, e.g. the index of the square it is computing
- Fixed a state issue that caused every other rectangle computation to fail
- Added random failures to show UI updates for a failing task
- Fixed progress update in the service-based tasks
- minor adjustments to layout code
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();
}
}
}