Java 如何从服务对象关闭一个阶段

Java how to close a stage from Service Object

我处于启动新视图的情况,但在其控制器中我向服务器发出请求以获取一些对象。这个请求需要一些时间,所以我想在这段等待时间内实现一个小的加载视图。因此,在我的控制器中,我实现了一个服务对象以在另一个线程中发出此请求:

 public void initialize(URL url, ResourceBundle resourceBundle) {
       
        try {
           

            Parent root = FXMLLoader.load(getClass().getResource("/MiniPages/LoadingPage.fxml"));

            Scene scene = new Scene(root);
            Stage primaryStage = new Stage();

            primaryStage.initStyle(StageStyle.UNDECORATED);


            Service<ProjectModel> service = new Service<ProjectModel>() {
                @Override
                protected Task<ProjectModel> createTask() {
                    return new Task<ProjectModel>() {
                        @Override
                        protected ProjectModel call() throws Exception {
                            SenderText data = new SenderText();
                            int id = Integer.parseInt(data.getData());

                            Client client = Client.getInstance();
                            JSONObject tosend = new JSONObject();

                            tosend.put("Type", "Get Project");
                            tosend.put("IDproject", id);

                            GsonBuilder gsonBuilder = new GsonBuilder();
                            gsonBuilder.registerTypeAdapter(LocalDate.class, new LocalDateSerializer());
                            gsonBuilder.registerTypeAdapter(LocalDate.class, new LocalDateDeserializer());
                            Gson gson = gsonBuilder.setPrettyPrinting().create();

                            client.sendText(tosend.toString());
                            String response = client.receiveText();

                            ProjectModel project = gson.fromJson(response, ProjectModel.class);
                            projectLocal = project;
                            System.out.println(gson.toJson(projectLocal));
                            primaryStage.close();
                            primaryStage.hide();
                            return project;
                        }
                    };

                }
            };
            service.start();

            primaryStage.setScene(scene);
            primaryStage.initModality(Modality.APPLICATION_MODAL);
            while (service.getValue() == null) {
                primaryStage.showAndWait();
            }
            primaryStage.close();

            System.out.println("Finish");
            primaryStage.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

我测试过,对象是在 call() 方法上设置的。但是加载视图不会关闭。我也尝试调用 primaryStage.show() 而不是 showandWait() 但它没有用。知道如何解决吗?

JavaFX 中有两个主要线程规则(类似于大多数其他 UI 工具包)。

  1. 您不得从后台线程对 UI 执行操作。这包括创建、显示或隐藏 windows.
  2. 您不得阻塞 UI 线程(“FX 应用程序线程”)。

您在 call() 方法中调用 primaryStage.close() 违反了第一条规则。

你在繁忙的 while 循环中违反了第二条规则。 (更糟;我认为该服务的 value 属性 仅在 FX 应用程序线程上更新。因此,由于您阻塞了该线程,因此 value 属性 不能已更新并且 while 循环永远不会退出。)

要在后台 Task 完成时执行 UI 操作,您可以使用 onSucceeded 处理程序。在后台任务成功完成后,在 FX 应用程序线程上调用此处理程序。

public void initialize(URL url, ResourceBundle resourceBundle) {
   
    try {

        Parent root = FXMLLoader.load(getClass().getResource("/MiniPages/LoadingPage.fxml"));

        Scene scene = new Scene(root);
        Stage primaryStage = new Stage();

        primaryStage.initStyle(StageStyle.UNDECORATED);


        Service<ProjectModel> service = new Service<ProjectModel>() {
            @Override
            protected Task<ProjectModel> createTask() {
                return new Task<ProjectModel>() {
                    @Override
                    protected ProjectModel call() throws Exception {
                        SenderText data = new SenderText();
                        int id = Integer.parseInt(data.getData());

                        Client client = Client.getInstance();
                        JSONObject tosend = new JSONObject();

                        tosend.put("Type", "Get Project");
                        tosend.put("IDproject", id);

                        GsonBuilder gsonBuilder = new GsonBuilder();
                        gsonBuilder.registerTypeAdapter(LocalDate.class, new LocalDateSerializer());
                        gsonBuilder.registerTypeAdapter(LocalDate.class, new LocalDateDeserializer());
                        Gson gson = gsonBuilder.setPrettyPrinting().create();

                        client.sendText(tosend.toString());
                        String response = client.receiveText();

                        ProjectModel project = gson.fromJson(response, ProjectModel.class);
                        return project;
                    }
                };

            }
        };

        service.setOnSucceeded(event -> {

            System.out.println("Finish");

            // I think; don't know what projectLocal is, or 
            // which threads it should be accessed from:
            projectLocal = project ;
            primaryStage.hide();
        });

        service.start();

        primaryStage.setScene(scene);
        primaryStage.showAndWait();
        
    } catch (Exception e) {
        e.printStackTrace();
    }
}

顺便说一句,您在这里可能不需要 ServiceService 真正为重复使用而设计,您只使用一次。只需在后台线程上创建一个 Task 和 运行 就足够了:

public void initialize(URL url, ResourceBundle resourceBundle) {
   
    try {

        Parent root = FXMLLoader.load(getClass().getResource("/MiniPages/LoadingPage.fxml"));

        Scene scene = new Scene(root);
        Stage primaryStage = new Stage();

        primaryStage.initStyle(StageStyle.UNDECORATED);


        Task<ProjectModel> task = new Task<ProjectModel>() {

            @Override
            protected ProjectModel call() throws Exception {
                SenderText data = new SenderText();
                int id = Integer.parseInt(data.getData());

                Client client = Client.getInstance();
                JSONObject tosend = new JSONObject();

                tosend.put("Type", "Get Project");
                tosend.put("IDproject", id);

                GsonBuilder gsonBuilder = new GsonBuilder();
                gsonBuilder.registerTypeAdapter(LocalDate.class, new LocalDateSerializer());
                gsonBuilder.registerTypeAdapter(LocalDate.class, new LocalDateDeserializer());
                Gson gson = gsonBuilder.setPrettyPrinting().create();

                client.sendText(tosend.toString());
                String response = client.receiveText();

                ProjectModel project = gson.fromJson(response, ProjectModel.class);
                return project;

            }
        };

        task.setOnSucceeded(event -> {

            System.out.println("Finish");

            // I think; don't know what projectLocal is, or 
            // which threads it should be accessed from:
            projectLocal = project ;
            primaryStage.hide();
        });

        new Thread(task).start();

        primaryStage.setScene(scene);
        primaryStage.showAndWait();
        
    } catch (Exception e) {
        e.printStackTrace();
    }
}

您的代码结构在这里看起来有点奇怪。看起来您需要在启动时为您的应用程序创建一个模型,并且该启动需要一些时间,因此您在加载时显示某种“启动画面”。这里奇怪的部分是您在 FXML 控制器的 initialize() 方法中执行此操作。作为生命周期方法 start() 的一部分,这确实应该在您的 Application class 中完成。结构应如下所示:

public class App extends Application {

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

    @Override
    public void start(Stage primaryStage) throws Exception {
        Task<ProjectModel> task = new Task<>() {
            @Override
            public ProjectModel call() throws Exception {
                return loadModel();
            }
        };

        Stage loadingStage = showLoadingStage();
        task.setOnSucceeded(e -> {
            loadingStage.hide();
            showMainStage(primaryStage, task.getValue());
        });

        new Thread(task).start();
    }

    private ProjectModel loadModel() throws Exception {
        // load model and return it....
    }

    private Stage showLoadingStage() throws Exception {
        Scene scene = new Scene(getClass().getResource("LoadingPage.fxml"));
        Stage loadingStage = new Stage();
        loadingStage.initStyle(StageStyle.UNDECORATED);
        loadingStage.setScene(scene);
        loadingStage.show();
        return loadingStage ;
    }

    private void showMainStage(Stage stage, ProjectModel model) throws Exception {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("Main.fxml"));
        Parent root = loader.load();
        MainController controller = loader.getController();
        controller.setModel(model);
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.show();
    }
}

其中 MainController 是具有 setModel(...) 方法的常规 FXML 控制器:

public class MainController {

    private ProjectModel model ;

    // @FXML-annotated fields

    @FXML
    private void initialize() {
        // initialization work that does not require model
    }

    public void setModel(ProjectModel model) {
        this.model = model ;
        // initialize any UI that depends on the model, etc.
    }
}