1

I am trying to show two stages simultaneously in JavaFX, where the first stage is supposed to be showing a progessbar and which should close as soon as the second stage is ready to show. I tried running both via Platform.runLater and via Tasks but the problem is that the stages are both frozen until both are finished loading and the progessbar starts being animated not until the second stage is finished loading.

Here some extract from the code:

public void start(Stage primaryStage) throws Exception {
        new Thread(progressTask()).start();
        new Thread(loginTask()).start();
    }



    public Task<Void> loginTask() {
        Task<Void> t = new Task<Void>() {

            @Override
            protected Void call() throws Exception {
                Platform.runLater(new Runnable() {

                    @Override
                    public void run() {

                        Stage s = new Stage();
                        FXMLLoader loader = new FXMLLoader(getClass().getResource("Login.fxml"));
                        Scene sc = null;
                        try {

                            sc = new Scene((Parent) loader.load());
                        } catch (IOException e) {
                            e.printStackTrace();
                        }

                        s.setScene(sc);
                        s.show();

                    }

                });
                return null;
            };
        };
        return t;
    }

    public Task<Void> progressTask() {

        Task<Void> t = new Task<Void>() {
            @Override
            protected Void call() throws Exception {
                Platform.runLater(new Runnable() {

                    @Override
                    public void run() {
                            ProgressBar bar = new ProgressBar(0);
                            bar.setPrefSize(200, 24);
                            Timeline task = new Timeline(new KeyFrame(Duration.ZERO, new KeyValue(bar.progressProperty(), 0)), new KeyFrame(
                                    Duration.seconds(5), new KeyValue(bar.progressProperty(), 1)));

                            VBox layout = new VBox(10);
                            layout.getChildren().setAll(bar);
                            Stage stage = new Stage(StageStyle.DECORATED);
                            stage.setScene(new Scene(layout));
                            stage.initModality(Modality.WINDOW_MODAL);
                            stage.show();
                            new Thread() {
                                public void run() {
                                    task.playFromStart();
                                }
                            }.start();
                            task.setOnFinished(new EventHandler<ActionEvent>() {
                                    @Override
                                    public void handle(ActionEvent event) {
                                        stage.close();


                                    }

                                });
                    }
            });
                return null;
        }


};

Thanks for your help.

oszd93
  • 193
  • 1
  • 2
  • 9
  • 1
    You have misunderstood what `Task` and `Platform.runLater()` do. Have a look at http://stackoverflow.com/questions/13784333/platform-runlater-and-task-javafx – James_D Nov 26 '14 at 13:32
  • If you want to create stages in tasks / threads you have to use Platform.runLater, otherwise you are not running on the Application Thread and it will exit. So i need both tasks and runlater – oszd93 Nov 26 '14 at 14:00
  • What is the Task for? You are not running any code in a background thread. – James_D Nov 26 '14 at 14:05
  • The stage I am loading performs database-communication in its initialization. This takes some seconds to perform, therefore it should be done in the background to avoid freezing the program and to show a loading animation in the meantime. – oszd93 Nov 26 '14 at 14:10
  • Sounds like your question is actually [how do I access a local database from JavaFX using concurrent tasks for database operations so that the UI remains responsive.](https://gist.github.com/jewelsea/4957967)?. – jewelsea Nov 26 '14 at 18:40

1 Answers1

3

You should create the Task just to perform the long running operation, and use the callbacks to update the UI. There's rarely any need to call Platform.runLater(...) yourself.

In your code the Tasks serve no purpose at all, because they just schedule all the code to run on the FX Application Thread with Platform.runLater(...). So effectively you create a new Thread just to ask some code to be executed on the current thread.

Here is an example that does the kind of thing I think you are looking to do:

import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class DataLoadingStageExample extends Application {

    @Override
    public void start(Stage primaryStage) {

        Scene scene = buildInitialUI();
        primaryStage.setScene(scene);

        Stage monitorStage = new Stage();

        Task<DataClass> dataLoadingTask = createDataLoadingTask();

        // update UI when dataLoadingTask finishes
        // this will run on the FX Application Thread
        dataLoadingTask.setOnSucceeded(event -> {
            DataClass data = dataLoadingTask.getValue();
            scene.setRoot(createUIFromData(data));
            monitorStage.hide();
        });

        buildProgressUI(monitorStage, dataLoadingTask);

        // manage stage layout:
        primaryStage.yProperty().addListener((obs, oldY, newY) -> monitorStage.setY(newY.doubleValue() - 100));
        primaryStage.setTitle("Application");

        // show both stages:
        monitorStage.show();
        primaryStage.show();

        // start data loading in a background thread:
        new Thread(dataLoadingTask).start();

    }

    private void buildProgressUI(Stage monitorStage,
            Task<?> task) {
        ProgressBar progressBar = new ProgressBar();
        progressBar.progressProperty().bind(task.progressProperty());

        Scene monitorScene = new Scene(new StackPane(progressBar), 400, 75);
        monitorStage.setScene(monitorScene);
        monitorStage.setTitle("Loading progress");
    }

    private Scene buildInitialUI() {
        Label loadingLabel = new Label("Loading...");
        StackPane root = new StackPane(loadingLabel);
        Scene scene = new Scene(root, 600, 400);
        return scene;
    }

    private Task<DataClass> createDataLoadingTask() {
        return new Task<DataClass>() {
            @Override
            public DataClass call() throws Exception {
                // mimic connecting to database
                for (int i=0; i < 100; i++) {
                    updateProgress(i+1, 100);
                    Thread.sleep(50);
                }
                return new DataClass("Data");
            }
        };
    }

    private Parent createUIFromData(DataClass data) {
        // obviously much more complex in real life
        Label label = new Label(data.getData());
        return new StackPane(label);
    }

    public static class DataClass {
        // obviously much more complex in real life
        private final String data ;
        public DataClass(String data) {
            this.data = data ;
        }
        public String getData() {
            return data ;
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}
James_D
  • 201,275
  • 16
  • 291
  • 322