在 Java 程序中调用 JavaFX 并等待在 运行 更多代码之前等待退出

Call JavaFX in Java program and wait for wait to exit before running more code

在我的 java 程序中,我为用户提供了一些选项,其中一个选项调用 JavaFXProgram 来显示某些内容。当这个被调用的 JavaFX 实际退出时,我只想在 Java 程序中 运行 更多代码,可能需要 5 秒,也可能需要一分钟。理想情况下,我想要的是我们在 Android 中拥有的东西。我们调用startActivityForResult(),然后等待onActivityResult()的调用。 我怎样才能在我的情况下实现类似的行为?

我写了这段代码来尝试重现我遇到的问题。这是类似的想法,但不知何故,这会调用 JavaFX,进入循环开始并毫无问题地检索用户的输入。在我的其他程序中,当它再次返回扫描输入时,我总是得到 Exception in thread "main" java.util.InputMismatchException。但正如我所说,理想情况下,我只想在 JavaFX 应用程序关闭后再编写 运行 代码。

package JavaCallsJavaFXandWaits;

import java.util.Scanner;
import javafx.application.Application;

public class MyJavaProgram {
    public static void main(String[] args) {
        int input;
        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.println("0 - exit");
            System.out.println("1 - display something to me");
            input = scanner.nextInt();
            switch (input) {
                case 0:
                    break;
                case 1:
                    Application.launch(JavaCallsJavaFXandWaits.MyJavaFXProgram.class, null);
                    // how to get notified of MyJavaFXProgram exit? I only want to run code after it exits
                    break;

            }
            if (input == 0) break;
        }


    }

}

package JavaCallsJavaFXandWaits;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class MyJavaFXProgram extends Application {

    @Override
    public void start(Stage primaryStage) {
        Text oText = new Text("My JavaFXProgram");

        StackPane root = new StackPane();
        root.getChildren().add(oText);

        Scene scene = new Scene(root, 800, 600);

        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

}

edit1:

我刚刚注意到,如果我尝试显示某些内容两次(例如:选择 1,关闭 JavaFX 应用程序,然后再次选择 1)它会崩溃并显示 Exception in thread "main" java.lang.IllegalStateException: Application launch must not be called more than once。 JavaFX 应用程序似乎也没有在此代码中正确退出。

您的代码无法正常工作,因为它不适合 JavaFX 应用程序 life-cycle,Application 的 API 文档中对此进行了完整记录.简而言之,Application class 代表 整个应用程序 ,(或者可能是应用程序的生命周期)。

要在 JavaFX 中显示 window,您必须在 FX 应用程序线程上执行此操作,并且必须启动 FX 工具包才能启动该线程(除其他外)。 Application.launch() 方法启动 FX 工具包,启动 FX 应用程序线程,创建应用程序实例 class,在该实例上调用 init(),然后在该实例上调用 start()实例(对 start() 的调用发生在 FX 应用程序线程上)。

documented 一样,Application.launch() 会阻塞(不会 return)直到 FX 工具包关闭(即应用程序退出),并且只能调用一次。 (因为它代表整个应用程序,所以这是有道理的,并且没有办法绕过调用它两次。)

从用户的角度来看,您的应用程序结构也没有任何意义。为什么要求您的用户与命令行交互以向 GUI-based 应用程序显示选项?您应该在 GUI 中显示这些选项。只需在启动时显示带有选项的 window,然后显示与所选选项对应的 window。如果你想确保用户在选择的选项完成之前不能 return 到原来的 window,只需将新的 window 设为模态即可。

例如,如果您重构 MyJavaFXProgram 所以它不是 Application subclass(您应该这样做,因为它不是应用程序的起点):

import javafx.scene.Parent;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Text;

public class MyJavaFXProgram  {

    private StackPane view ;

    public MyJavaFXProgram() {
        Text oText = new Text("My JavaFXProgram");

        view = new StackPane();
        view.getChildren().add(oText);

    }

    public Parent getView() {
        return view ;
    }

}

那你就可以了

import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Stage;

public class MyJavaProgram extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        Button showMeSomethingButton = new Button("Show me something");
        showMeSomethingButton.setOnAction(e -> {
            MyJavaFXProgram myProgram = new MyJavaFXProgram();
            showInModalWindow(myProgram.getView());
        });
        Button exitButton = new Button("Exit");
        exitButton.setOnAction(e -> Platform.exit());

        VBox root = new VBox(10, exitButton, showMeSomethingButton);
        root.setPadding(new Insets(20));
        root.setAlignment(Pos.CENTER);
        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private void showInModalWindow(Parent view) {
        Stage stage = new Stage();
        stage.initModality(Modality.APPLICATION_MODAL);
        stage.setScene(new Scene(view));
        stage.show();
    }


}

如果你真的想从命令行驱动所有这些(老实说,我看不出这样做的正当理由),那么它会变得棘手,你必须亲自动手管理它们之间的交互线程在某些时候。可能最简单的方法是确保 FX 工具包在您的应用程序启动时启动,然后或多或少地按照您在代码中的方式进行,但是 MyJavaFXProgram 再次重构,因此它不是 subclass 共 Application。有一个 hack 可以通过创建 JFXPanel 来启动 FX 工具包,但这确实有点 hack,我更喜欢通过 Application class 来明确地做到这一点' 实际上做任何事情,调用 launch(),并等待其初始化完成。我在对 的回答中展示了如何执行此操作,因此我将从那里借用该解决方案。这是用于启动 FX 工具包的 Application class(但不是您执行的主要 class):

import java.util.concurrent.CountDownLatch;

import javafx.application.Application;
import javafx.stage.Stage;

public class FXStarter extends Application {

    private static final CountDownLatch latch = new CountDownLatch(1);

    public static void awaitFXToolkit() throws InterruptedException {
       latch.await();
    }

    @Override
    public void init() {
        latch.countDown();
    }

    @Override
    public void start(Stage primaryStage) {
        // no-op
    }
}

想法是调用 Application.launch(FXStarter.class),但由于 launch() 块,您需要在后台线程上执行此操作。因为这意味着您的后续代码可能(很可能会)在 launch() 实际完成您需要它完成的工作之前执行,您需要等到它完成它的工作,这可以用 FXStarter.awaitFXToolkit() 完成。然后你可以执行你的循环。唯一需要担心的事情是确保您在 FX 应用程序线程上创建并显示新的 windows。所以你的 MyJavaProgram 现在看起来像:

import java.util.Scanner;
import java.util.concurrent.FutureTask;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class MyJavaProgram {
    public static void main(String[] args) throws Exception {

        // start FX toolkit on background thread:
        new Thread(() -> Application.launch(FXStarter.class)).start();
        // wait for toolkit to start:
        FXStarter.awaitFXToolkit();

        // make sure closing first window does not exit FX toolkit:
        Platform.setImplicitExit(false);

        int input;
        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.println("0 - exit");
            System.out.println("1 - display something to me");
            input = scanner.nextInt();
            switch (input) {
                case 0:
                    break;
                case 1:
                    // task to show UI:
                    FutureTask<Void> showProgramTask = new FutureTask<>(() -> {
                        MyJavaFXProgram program = new MyJavaFXProgram();
                        Stage stage = new Stage();
                        stage.setScene(new Scene(program.getView(), 400, 400));
                        stage.setOnShown(e -> {
                            stage.toFront();
                            stage.requestFocus();
                        });
                        // showAndWait will block execution until window is hidden:
                        stage.showAndWait();
                        return null ;
                    });
                    // show UI on FX Application Thread:
                    Platform.runLater(showProgramTask);
                    // block until task completes (i.e. window is hidden):
                    showProgramTask.get() ;
                    break;

            }
            if (input == 0) break;
        }

        // all done, exit FX toolkit:
        Platform.exit();
        scanner.close();
    }

}

(这使用与上面 MyJavaFXProgram 相同的版本。)