1

在我的 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);
    }

}

编辑1:

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

4

1 回答 1

5

您的代码无法正常工作,因为它不适合 JavaFX 应用程序生命周期,该生命周期在Application. 简而言之,Application类代表整个应用程序,(或者可能是应用程序的生命周期)。

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

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

从用户的角度来看,您的应用程序结构也没有任何意义。为什么要求您的用户与命令行交互以向基于 GUI 的应用程序提供选项?您应该在 GUI 中显示这些选项。只需在启动时显示一个带有选项的窗口,然后显示一个与所选选项相对应的窗口。如果要确保用户在所选选项完成之前不能返回到原始窗口,只需将新窗口设置为模态即可。

例如,如果你重构MyJavaFXProgram它使其不是Application子类(你应该这样做,因为它不是应用程序的起点):

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再次重构,使其不是Application. 有一个 hack 来启动 FX 工具包JFXPanelApplication通过launch()创建完全的。我在回答这个问题时展示了如何做到这一点,所以我会从那里借用那个解决方案。这Application是用于启动 FX 工具包的类(但不是您执行的主类):

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 应用程序线程上创建并显示新窗口。所以你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上述相同的版本。)

于 2017-01-22T00:25:06.517 回答