1

我最初的 fxml(say home.fxml) 有很多功能,因此需要很长时间才能完全加载。因此,为了避免程序启动和 fxml 加载之间的时间间隔,我引入了一个loader.fxml带有 gif 图像的 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 图像没有移动)。如果我在外部加载 fxml Platform.runLater(),我会得到异常Not on FX Thread

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

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

4

3 回答 3

5

使用Task. 您需要安排在 FX 应用程序线程上创建场景并更新舞台。最干净的方法是使用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();

这是使用此技术的 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();
    }
}

测试.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 保持响应状态。

于 2016-01-19T14:04:07.123 回答
0

您使用的方法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();
于 2016-01-19T11:39:01.643 回答
0

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

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

于 2016-01-21T06:13:49.390 回答