Reputation: 193
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
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 Task
s 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