Load fxml as background process - Javafx

后端 未结 3 1195
滥情空心
滥情空心 2021-01-16 02:41

My initial fxml(say home.fxml) has a lot of functionalities, hence it takes a lot of time to load completely. So to avoid the time gap between program start and

相关标签:
3条回答
  • 2021-01-16 03:14

    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.

    0 讨论(0)
  • 2021-01-16 03:31

    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.

    0 讨论(0)
  • 2021-01-16 03:33

    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();
    
    0 讨论(0)
提交回复
热议问题