Ваш код не работает так, как у вас есть, потому что он не соответствует жизненному циклу приложения JavaFX, который полностью задокументирован в документации по API для Application
. Короче говоря, класс Application
представляет все приложение (или, возможно, жизненный цикл приложения).
Чтобы отобразить окно в JavaFX, вы должны сделать это в потоке приложения FX, а для запуска этого потока должен быть запущен инструментарий FX (среди прочего). Метод Application.launch()
запускает инструментарий FX, запускает поток приложения FX, создает экземпляр класса вашего приложения, вызывает init()
в этом экземпляре, а затем вызывает start()
в этом экземпляре (вызов start()
происходит в потоке приложения FX).
Как задокументировано, Application.launch()
блокирует (не возвращает) до тех пор, пока инструментарий FX не завершит работу (т. е. приложение не закроется), и должен вызываться только один раз. (Поскольку он представляет все приложение, это имеет смысл, и нет возможности обойти его дважды.)
Структура вашего приложения также не имеет никакого смысла с точки зрения пользователя. Зачем просить вашего пользователя взаимодействовать с командной строкой, чтобы представить параметры для приложения на основе графического интерфейса? Вы должны представить эти параметры в графическом интерфейсе. Просто покажите окно с параметрами при запуске, а затем покажите окно, соответствующее выбранному параметру. Если вы хотите гарантировать, что пользователь не сможет вернуться в исходное окно до завершения выбранной опции, просто сделайте новое окно модальным.
Например, если вы реорганизуете 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
. Существует хак для запуска инструментария 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
, что и выше.)
person
James_D
schedule
22.01.2017
showAndWait()
? - person Bo Halim   schedule 21.01.2017Application.launch()
не возвращается, пока приложение JavaFX не завершит работу. Но также в целом этот подход не будет работать, потому что вы не можете вызыватьlaunch()
более одного раза. - person James_D   schedule 21.01.2017showAndWait()
на самом деле здесь не работает (по крайней мере, напрямую). Конечно, вы можете вызыватьshowAndWait()
только из потока приложения FX, и в частности он должен вызываться из обработчика событий или вызоваPlatform.runLater()
, и нельзя вызывать на основном этапе. Конечно, отправка его наPlatform.runLater()
позволяет продолжить работу с текущим кодом. Таким образом, вы должны сделать это с некоторой умеренно сложной многопоточностью. - person James_D   schedule 21.01.2017Application.launch()
не возвращается, пока не завершится JavaFX. У меня была ошибка, которая все испортила, но теперь, как вы уже сказали, я столкнулся с проблемой вызоваlaunch()
более одного раза. Я не понимаю, почему это проблема. Я вызываю приложение, но затем оно закрывается, так как же оно узнает, что оно вызывается дважды? Есть ли способ сбросить это? - person power_output   schedule 21.01.2017