您的代码无法正常工作,因为它不适合 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 工具包JFXPanel
,Application
通过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
上述相同的版本。)