Execute task in background in JavaFX

I want to load up to nine panels in a TilePane. For each pane I have to first run a computation of the content (about 300ms) and then I have to build the Panel (about 500ms).

What I want is, that there are nine ProgressIndicators which exchanges with every panel after its computation.

I tried it with the Platform.runLater command as well as with a service class. The result was always the same. The ProgressIndicator are shown, but not animated. After seconds there are all panels at once.

Is there a possibility, that the Indicators are animated the whole time and that I can exchange them one after another?


Solution 1:

JavaFX has Event Dispatch Thread which it uses for UI events. All work with UI should happen on this thread. And non-UI calculations shouldn't happen there to avoid lags in UI.

See next code:

public class Indicators extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) {
        Pane root = new HBox();
        stage.setScene(new Scene(root, 300, 100));

        for (int i = 0; i < 10; i++) {
            final ProgressIndicator pi = new ProgressIndicator(0);
            root.getChildren().add(pi);

            // separate non-FX thread
            new Thread() {

                // runnable for that thread
                public void run() {
                    for (int i = 0; i < 20; i++) {
                        try {
                            // imitating work
                            Thread.sleep(new Random().nextInt(1000));
                        } catch (InterruptedException ex) {
                            ex.printStackTrace();
                        }
                        final double progress = i*0.05;
                        // update ProgressIndicator on FX thread
                        Platform.runLater(new Runnable() {

                            public void run() {
                                pi.setProgress(progress);
                            }
                        });
                    }
                }
            }.start();
        }

        stage.show();

    }
}

Solution 2:

This is how I solved the problem:

import java.util.Random;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.concurrent.Worker.State;
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.stage.Stage;


public class Minimal extends Application {

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;

@Override
public void start(Stage primaryStage) throws Exception {
    //creating Layout
    final Group root = new Group();                             
    Scene scene = new Scene(root, 400, 400);            
    primaryStage.setScene(scene);
    primaryStage.setResizable(false);
    StackPane waitingPane = new StackPane();
    final ProgressBar progress = new ProgressBar();
    Label load = new Label("loading things...");
    progress.setTranslateY(-25);
    load.setTranslateY(25);
    waitingPane.getChildren().addAll(new Rectangle(400,400,Color.WHITE),load,progress);
    root.getChildren().add(waitingPane);

    //Task for computing the Panels:
    Task<Void> task = new Task<Void>() {
        @Override
        protected Void call() throws Exception {
            for (int i = 0; i < 20; i++) {
                try {
                    Thread.sleep(new Random().nextInt(1000));
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
                final double prog = i*0.05;
                Platform.runLater(new Runnable() {
                    public void run() {
                        progress.setProgress(prog);
                    }
                });
            }
            return null;
        }
    };

    //stateProperty for Task:
    task.stateProperty().addListener(new ChangeListener<Worker.State>() {

        @Override
        public void changed(ObservableValue<? extends State> observable,
                State oldValue, Worker.State newState) {
            if(newState==Worker.State.SUCCEEDED){
                loadPanels(root);
            }
        }
    });

    //start Task
    new Thread(task).start();

    primaryStage.show();
}

private void loadPanels(Group root) {
    //change to loadPanel:
    root.getChildren().set(0,createLoadPane());

    //Service:
    final Service<Rectangle> RecBuilder = new Service<Rectangle>() {
          @Override protected Task<Rectangle> createTask() {
              return new Task<Rectangle>() {
                  @Override protected Rectangle call() throws InterruptedException {
                      updateMessage("loading rectangle . . .");
                      updateProgress(0, 10);
                      for (int i = 0; i < 10; i++) {
                        Thread.sleep(100);
                      }
                      updateMessage("Finish!");
                      return new Rectangle((380)/3,(380)/3,colors[counter]);
                  }
              };
          }
    };

    //StateListener
    RecBuilder.stateProperty().addListener(new ChangeListener<Worker.State>() {
        @Override public void changed(ObservableValue<? extends Worker.State> observableValue,
                        Worker.State oldState, Worker.State newState) {
            switch (newState) {
            case SCHEDULED:
                break;
            case READY:
            case RUNNING:
                break;
            case SUCCEEDED:
                Rectangle rec = RecBuilder.valueProperty().getValue();
                indicators[counter].progressProperty().unbind();
                loading[counter].textProperty().unbind();
                loadPane.getChildren().set(counter, rec);
                if(counter<8){
                    counter++;
                    nextPane(RecBuilder);
                }
                break;
            case CANCELLED:
            case FAILED:
                loading[counter].textProperty().unbind();
                loading[counter].setText("Failed!");
                if(counter<8){
                    counter++;
                    nextPane(RecBuilder);
                }
                break;
            }
        }
    });

    //begin PanelBuilding:
    nextPane(RecBuilder);
}

private void nextPane(Service<Rectangle> recBuilder) {
    loading[counter].textProperty().bind(recBuilder.messageProperty());
    indicators[counter].visibleProperty().bind(recBuilder.progressProperty().isNotEqualTo(new SimpleDoubleProperty(ProgressBar.INDETERMINATE_PROGRESS)));
    recBuilder.restart();
}

private Node createLoadPane() {
    loadPane = new TilePane(5,5);
    loadPane.setPrefColumns(3);
    loadPane.setPadding(new Insets(5));
    for(int i=0;i<9;i++){
        StackPane waitingPane = new StackPane();
        Rectangle background = new Rectangle((380)/3, (380)/3, Color.WHITE);
        indicators[i] = new ProgressIndicator();
        indicators[i].setPrefSize(50, 50);
        indicators[i].setMaxSize(50, 50);
        indicators[i].setTranslateY(-25);
        indicators[i].setTranslateX(-10);
        loading[i] = new Label();
        loading[i].setTranslateY(25);
        waitingPane.getChildren().addAll(background,indicators[i],loading[i]);
        loadPane.getChildren().add(waitingPane);
    }

    return loadPane;
}

public static void main(String[] args) {
    launch(args);
}

}

Solution 3:

The easiest way is by using lambdas and thread.

private void logIn() {
    new Thread(() -> {
        //CONNECT TO WEB AND LOG IN (ON OUT OF FX THREAD)
        boolean loginSuccess = new LogIn(emial.getText(), pass.getText()).execute();

        //DO SOMETHING WITH CONTROLLS ON FX THREAD ACCORDING RESULT OF OVER
        Platform.runLater(() -> {
            if (loginSuccess) {
                info.setText("SUCCESS");
                info.setTextFill(Color.web("#268515"));
            } else {
                info.setText("FAIL");
                info.setTextFill(Color.web("#CD000E"));
            }
        });
    }).start();
}