【问题标题】:Load fxml as background process - Javafx加载 fxml 作为后台进程 - Javafx
【发布时间】:2016-01-19 09:58:37
【问题描述】:

我最初的 fxml(比如home.fxml)有很多功能,因此需要很长时间才能完全加载。因此,为了避免程序启动和 fxml 加载之间的时间间隔,我引入了一个带有 gif 图像的 fxml(比如loader.fxml),该图像应该在主 fxml 加载时出现。 问题是我的 loader.fxml 中的 gif 图像没有移动,因为程序挂起,直到 home.fxml 完全加载。 为了避免这种情况,我将 home.fxml 加载移动到一个线程中,如下面的代码所示。

public class UATReportGeneration extends Application {

    private static Stage mainStage;

    @Override
    public void start(Stage stage) {
        Parent loaderRoot = null;
        try {
            loaderRoot = FXMLLoader.load(getClass().getResource("/uatreportgeneration/fxml/Loader.fxml"));
        } catch (IOException ex) {
            Logger.getLogger(UATReportGeneration.class.getName()).log(Level.SEVERE, null, ex);
        }
        Scene loadScene = new Scene(loaderRoot);
        stage.setScene(loadScene);
        stage.initStyle(StageStyle.UNDECORATED);
        stage.getIcons().add(new Image(this.getClass().getResourceAsStream("/uatreportgeneration/Images/logo.png")));

        stage.show();


        mainStage = new Stage(StageStyle.UNDECORATED);
        mainStage.setTitle("Upgrade Analysis");
        mainStage.getIcons().add(new Image(this.getClass().getResourceAsStream("/uatreportgeneration/Images/logo.png")));
        setStage(mainStage);

        new Thread(() -> {
            Platform.runLater(() -> {
                try {
                    FXMLLoader loader = new FXMLLoader();
                    Parent root = loader.load(getClass().getResource("/uatreportgeneration/fxml/Home.fxml"));

                    Scene scene = new Scene(root);
                    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");
                } catch (IOException ex) {
                    Logger.getLogger(UATReportGeneration.class.getName()).log(Level.SEVERE, null, ex);
                }

            });
        }).start();

    }

    public static Stage getStage() {
        return mainStage;
    }

    public static void setStage(Stage stage) {
        mainStage = stage;
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {

        launch(args);
    }

}

但是在这段代码之后,我的程序也挂了(gif 图像没有移动)。如果我在 Platform.runLater() 之外加载 fxml,我会收到异常 Not on FX Thread

我也厌倦了使用Task(),但是如果我尝试在Platform.runLater()之外加载fxml,gif图像正在移动但fxml没有在后台加载。

谁能帮助我并告诉我如何更正代码,以便我的 fxml 在后台加载而不会干扰前台进程。

【问题讨论】:

    标签: javafx task fxml fxmlloader


    【解决方案1】:

    使用Task。您需要安排在 FX 应用程序线程上创建场景并更新舞台。最干净的方法是使用Task&lt;Parent&gt;:

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

    这是一个使用这种技术的 SSCCE:

    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(使用上面显示的Task 方法):

    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>
    

    测试控制器:

    import javafx.fxml.FXML;
    import javafx.scene.control.Label;
    
    public class TestController {
        @FXML
        private Label label ;
        @FXML
        private void closeWindow() {
            label.getScene().getWindow().hide();
        }
    }
    

    主要应用:

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

    请注意,按下按钮后,您仍然可以在“加载”FXML 所需的五秒钟内输入文本字段,因此 UI 保持响应状态。

    【讨论】:

    • 我已经尝试过Task&lt;Parent&gt;。它给了我例外IllegalStateException: Not on FX application thread.. 你能告诉我如何在 FX 应用程序线程上执行它吗?使用Platform.runLater 是挂第一个fxml。 :(
    • 确保不要在后台线程中创建场景或舞台。它们必须在 FX 应用程序线程上创建,如此处的示例代码所示。
    • 我试过你上面提到的代码。仍然给我同样的例外。
    • 那么您要么在 FXML 中创建 SceneStage,要么在控制器中执行 FX 应用程序线程上不允许的操作。如上所述使用Task 是正确的通用方法;你只是有一些细节错误。编辑您的问题以显示您对该方法的尝试并包含您获得的完整堆栈跟踪。
    • 如果您仍然遇到异常,这里有一个错误说有a few components 除了SceneStage 不能 在 FX 应用程序线程之外创建。 TooltipContextMenu 从 8u40 开始应该可以工作,但 WebView 尚未解决。
    【解决方案2】:

    您使用Task 的方法已经正确。你只是错过了一点:你只是错过了另一个 Platform#invokeLater() 来更新 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();
    

    【讨论】:

    • 我试过你的代码。它甚至没有进入home.fxml 的控制器内部。 gif 正在按预期移动,但 home.fxml 未加载。
    • 不应该盲目添加。虽然这是我的工作示例:pastebin.com/RThnUe7i
    • 您正在压制所有异常。将setOnFailed 处理程序添加到任务中,看看它是否在抛出任何东西。我认为您可以通过在后台线程上创建Scene 来获得IllegalStateException
    • @KnusperPudding 我没有盲目地添加它......我做了你建议的所有更改。仍然给出相同的结果。
    • @HARSHITASETHI 你试过这个例子吗?您的主要问题是使用ThreadRunnable。顺便说一句,James_D 的回答要好得多。
    【解决方案3】:

    除了@James_D 的回答。正如他所说,Stages和Scene不应该在后台线程中添加,它们应该只在FX主线程中添加。

    我在我的home.fxml 中添加了tooltips,这不过是PopupWindow。因此对于后台线程来说,它作为一个新的阶段出现了。因此它抛出了IllegalStateException。从 fxml 中删除 tooltips 后,fxml 能够作为后台进程加载,因为该线程中没有创建任何阶段。

    【讨论】:

      猜你喜欢
      • 2016-11-07
      • 2017-02-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-21
      • 2017-03-17
      • 1970-01-01
      相关资源
      最近更新 更多