在Java程序中调用JavaFX并等待退出,然后再运行更多代码 [英] Call JavaFX in Java program and wait for wait to exit before running more code

查看:103
本文介绍了在Java程序中调用JavaFX并等待退出,然后再运行更多代码的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的Java程序中,我给用户一些选项,其中之一调用JavaFXProgram来显示某些内容.当被调用的JavaFX实际退出时,我只想在Java程序中运行更多代码,可能需要5秒钟,也可能需要1分钟.理想情况下,我想要的是类似于Android的设备.我们调用startActivityForResult(),然后等待onActivityResult()的调用. 在我的情况下如何实现类似的行为?

In my java program i give some options to the user and one of thems calls a JavaFXProgram to display something. I only want to run more code in the Java program when this JavaFX that got called actually exits, it may take 5 seconds, it may take a minute. Ideally what i would like is something like we have in Android. We call startActivityForResult() and then wait for the call of onActivityResult(). How can i achieve similar behaviour in my situation?

我有这段代码是我写的,试图复制我遇到的问题.这是类似的想法,但是以某种方式将其称为JavaFX,开始循环并毫无问题地从用户检索输入.在我的其他程序中,当它再次返回以扫描输入时,我总是得到Exception in thread "main" java.util.InputMismatchException.但是正如我所说,理想情况下,我只想在JavaFX Application关闭后运行更多代码.

I have this code that i wrote to try to replicate the problem i has having. It's similar idea but somehow this calls JavaFX, goes to start of loop and retrieves input from the user without a problem. In my other program i always get Exception in thread "main" java.util.InputMismatchException when it goes back again to scan for input. But as i said, ideally, i would like to only run more code after JavaFX Application closes.

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,关闭JavaFX应用程序,然后再次选择1),它将崩溃并显示Exception in thread "main" java.lang.IllegalStateException: Application launch must not be called more than once.似乎该代码中的JavaFX Application也未正确退出.

I just noticed that if i try to display something two times (eg: choose 1, close JavaFX application, then choose 1 again) it crashes with Exception in thread "main" java.lang.IllegalStateException: Application launch must not be called more than once. It seems that JavaFX Application is not exiting properly in this code too.

推荐答案

您的代码无法正常使用,因为它不适合JavaFX Application生命周期,API文档中已对此进行了充分说明用于 Application .简而言之,Application类表示整个应用程序(或应用程序的生命周期).

Your code doesn't work as you have it, because it doesn't fit in the JavaFX Application life-cycle, which is fully documented in the API documentation for Application. In brief, the Application class represents the entire application, (or perhaps the lifecycle of the application).

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

To show a window in JavaFX, you must do so on the FX application thread, and the FX toolkit must be started in order to start this thread (among other things). The Application.launch() method starts the FX toolkit, starts the FX application thread, creates an instance of your application class, calls init() on that instance, and then calls start() on that instance (the call to start() happens on the FX Application Thread).

As documented, Application.launch() blocks (does not return) until the FX toolkit shuts down (i.e. the application exits), and must be called only once. (Since it represents the entire application, this makes sense, and there is no way to work around calling it twice.)

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

Your application structure doesn't really make any sense from a user perspective either. Why ask your user to interact with the command line to present options to a GUI-based application? You should present those options in the GUI. Just show a window with the options at startup, and then show a window corresponding to the option chosen. If you want to ensure that the user cannot return to the original window before the chosen option is complete, simply make the new window modal.

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

For example, if you refactor MyJavaFXProgram so it is not an Application subclass (which you should do, since it is not the starting point of the 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的子类. .通过创建JFXPanel可以启动FX工具包,但是确实有点黑客,我更喜欢通过拥有Application类而不执行任何操作来明确地做到这一点,调用launch(),并等待其初始化完成.我在回答这个问题的答案中展示了如何做到这一点,所以我只从中借用该解决方案那里.这是用于启动FX工具包的Application类(但不是您执行的主类):

If you really want to drive all this from the command line (and I honestly can't see a valid reason to do that), then it gets tricky and you necessarily have to get your hands dirty with managing interactions between threads at some point. Probably the simplest approach is to make sure the FX toolkit starts up when your application starts, and then to proceed more or less as you have in your code, but with MyJavaFXProgram refactored again so that it is not a subclass of Application. There is a hack to start the FX toolkit, by creating a JFXPanel, but that really is a bit of a hack and I prefer to do it explicitly by having an Application class that doesn't actually do anything, calling launch(), and waiting for its initialization to complete. I showed how to do this in an answer to this question, so I'll just borrow that solution from there. Here is the Application class that is used to start the FX toolkit (but is not the main class that you execute):

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 Application Thread上创建并显示新窗口.因此,您的MyJavaProgram现在看起来像:

The idea is to call Application.launch(FXStarter.class), but since launch() blocks, you need to do that on a background thread. Since that means your ensuing code might (probably will) execute before launch() has actually completed the work you need it to complete, you need to wait until it's done its job, which you can do with FXStarter.awaitFXToolkit(). Then you can execute your loop. The only remaining thing to worry about is to make sure that you create and show new windows on the FX Application Thread. So your MyJavaProgram now looks like:

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相同的版本.)

(This uses the same version of MyJavaFXProgram above.)

这篇关于在Java程序中调用JavaFX并等待退出,然后再运行更多代码的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆