oszd93
oszd93

Reputation: 193

JavaFX Creating two Stages at the same time

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.

Upvotes: 1

Views: 5371

Answers (1)

James_D
James_D

Reputation: 209330

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

Upvotes: 3

Related Questions