在我的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应用程序在此代码中也没有正确退出。
答案 0 :(得分:5)
您的代码并不像您拥有的那样有效,因为它不适合JavaFX应用程序生命周期,这在Application
的API文档中有详细说明。简而言之,Application
类代表整个应用程序(或者可能是应用程序的生命周期)。
要在JavaFX中显示一个窗口,您必须在FX应用程序线程上执行此操作,并且必须启动FX工具包才能启动此线程(以及其他内容)。 Application.launch()
方法启动FX工具包,启动FX应用程序线程,创建应用程序类的实例,在该实例上调用init()
,然后在该实例上调用start()
(调用至start()
发生在FX应用程序线程上。
作为documented,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
以使其不是{的子类{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
的相同版本。)