在Java程序中调用JavaFX并等待等待退出,然后再运行更多代码

时间:2017-01-21 13:38:50

标签: java javafx wait onactivityresult startactivityforresult

在我的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应用程序在此代码中也没有正确退出。

1 个答案:

答案 0 :(得分:5)

您的代码并不像您拥有的那样有效,因为它不适合JavaFX应用程序生命周期,这在Application的API文档中有详细说明。简而言之,Application类代表整个应用程序(或者可能是应用程序的生命周期)。

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

作为documentedApplication.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以使其不是{的子类{1}}。通过创建Application来启动FX工具包是一个黑客攻击,但这确实是一个黑客攻击,我更喜欢通过拥有一个JFXPanel类没有明确地做到这一点实际做任何事情,调用Application,并等待其初始化完成。我在回答this question时展示了如何做到这一点,所以我只是从那里借用那个解决方案。以下是用于启动FX工具包的launch()类(但不是您执行的主类):

Application

我的想法是调用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()实际完成你需要它完成的工作之前执行,你需要等到它完成它的工作,你可以用{{{ 1}}。然后你可以执行你的循环。唯一需要担心的是确保在FX应用程序线程上创建并显示新窗口。所以你的launch()现在看起来像是:

FXStarter.awaitFXToolkit()

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