Load fxml as background process - Javafx

百般思念 提交于 2019-12-01 12:12:50

Use a Task. You need to arrange to create the scene and update the stage on the FX Application Thread. The cleanest way is to use a Task<Parent>:

Task<Parent> loadTask = new Task<Parent>() {
    @Override
    public Parent call() throws IOException {
        FXMLLoader loader = new FXMLLoader();
        Parent root = loader.load(getClass().getResource("/uatreportgeneration/fxml/Home.fxml"));
        return root ;
    }
};

loadTask.setOnSucceeded(e -> {
    Scene scene = new Scene(loadTask.getValue());

    mainStage.setScene(scene);
    mainStage.show();
    stage.hide();
    System.out.println("Stage showing");
    // Get current screen of the stage
    ObservableList<Screen> screens = Screen.getScreensForRectangle(new Rectangle2D(mainStage.getX(), mainStage.getY(), mainStage.getWidth(), mainStage.getHeight()));
    // Change stage properties
    Rectangle2D bounds = screens.get(0).getVisualBounds();
    mainStage.setX(bounds.getMinX());
    mainStage.setY(bounds.getMinY());
    mainStage.setWidth(bounds.getWidth());
    mainStage.setHeight(bounds.getHeight());
    System.out.println("thread complete");
});

loadTask.setOnFailed(e -> loadTask.getException().printStackTrace());

Thread thread = new Thread(loadTask);
thread.start();

Here is a SSCCE using this technique:

main.fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.VBox?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TextField?>

<VBox spacing="10" xmlns:fx="http://javafx.com/fxml/1" fx:controller="MainController">
    <padding>
        <Insets top="24" left="24" right="24" bottom="24"/>
    </padding>
    <TextField />
    <Button fx:id="button" text="Show Window" onAction="#showWindow"/>
</VBox>

MainController (uses the Task approach shown above):

import java.io.IOException;

import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.Stage;

public class MainController {
    @FXML
    private Button button ;
    @FXML
    private void showWindow() {
        Task<Parent> loadTask = new Task<Parent>() {
            @Override
            public Parent call() throws IOException, InterruptedException {

                // simulate long-loading process:
                Thread.sleep(5000);

                FXMLLoader loader = new FXMLLoader(getClass().getResource("test.fxml"));
                Parent root = loader.load();
                return root ;
            }
        };

        loadTask.setOnSucceeded(e -> {
            Scene scene = new Scene(loadTask.getValue());
            Stage stage = new Stage();
            stage.initOwner(button.getScene().getWindow());
            stage.setScene(scene);
            stage.show();
        });

        loadTask.setOnFailed(e -> loadTask.getException().printStackTrace());

        Thread thread = new Thread(loadTask);
        thread.start();
    }
}

test.fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Button?>
<?import javafx.geometry.Insets?>

<BorderPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="TestController">
    <padding>
        <Insets top="24" left="24" right="24" bottom="24"/>
    </padding>
    <center>
        <Label fx:id="label" text="This is a new window"/>
    </center>
    <bottom>
        <Button text="OK" onAction="#closeWindow" BorderPane.alignment="CENTER">
            <BorderPane.margin>
                <Insets top="5" bottom="5" left="5" right="5"/>
            </BorderPane.margin>
        </Button>
    </bottom>
</BorderPane>

TestController:

import javafx.fxml.FXML;
import javafx.scene.control.Label;

public class TestController {
    @FXML
    private Label label ;
    @FXML
    private void closeWindow() {
        label.getScene().getWindow().hide();
    }
}

Main application:

import java.io.IOException;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws IOException {
        primaryStage.setScene(new Scene(FXMLLoader.load(getClass().getResource("main.fxml"))));
        primaryStage.show();
    }

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

Notice that after pressing the button, you can still type in the text field during the five seconds it takes to "load" the FXML, so the UI is remaining responsive.

your approach using Task was already correct. you were just missing a bit more: you were just missing another Platform#invokeLater() to update the UI:

    new Thread(new Task() {

        @Override
        protected Object call() throws Exception {
            // Simulating long loading
            Thread.sleep(5000);

            FXMLLoader loader = new FXMLLoader(getClass().getResource("home.fxml"));
            Parent root = loader.load();

            Scene scene = new Scene(root);

            // Updating the UI requires another Platform.runLater()
            Platform.runLater(new Runnable() {

                @Override
                public void run() {
                    mainStage.setScene(scene);
                    mainStage.show();
                    stage.hide();
                    System.out.println("Stage showing");
                    // Get current screen of the stage
                    ObservableList<Screen> screens = Screen.getScreensForRectangle(new Rectangle2D(mainStage.getX(), mainStage.getY(), mainStage.getWidth(), mainStage.getHeight()));
                    // Change stage properties
                    Rectangle2D bounds = screens.get(0).getVisualBounds();
                    mainStage.setX(bounds.getMinX());
                    mainStage.setY(bounds.getMinY());
                    mainStage.setWidth(bounds.getWidth());
                    mainStage.setHeight(bounds.getHeight());
                    System.out.println("thread complete");
                }
            });
            return null;
        }
    }).start();

In addition to @James_D answer. As mentioned by him, Stages and Scene should not be added in background thread, they should be added only in FX main thread.

I have added tooltips in my home.fxml, which is nothing but a PopupWindow. Therefore to the background thread, it appeared as a new stage. Hence it threw IllegalStateException. After removing the tooltips from the fxml, the fxml was able to load as a background process as there were no stages created in that thread.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!