Я предполагаю, что этот дизайн был мотивирован (огромным) числом приложений Swing, которые были неправильно написаны, причем «первичный» JFrame
s был создан и показан на неправильной нити (то есть не на потоке отправки событий AWT). Я предполагаю, что так много приложений Swing были неправильно написаны, что им пришлось защищать фреймворк от неправильного использования и что они хотели избежать этого сценария с помощью JavaFX.
Форсирование (ну, почти форсирование, есть хаки), приложение FX для запуска этого способа затрудняет неправильное создание приложения аналогичным образом. launch
метод (и эквивалентный процесс запуска Oracle JVM, если у вас есть Application
подкласса без метода main
и призыва к launch
) делают совсем немного шаблонную работу: он запускает FX инструментарий, инстанцирует Application
подкласса и вызывает его метод init()
, то в потоке приложения FX он создает первичный Stage
и передает его методу Application
подкласса start(...)
. Затем это гарантирует, что все работает на правильной нити.
Вы должны в основном рассмотреть метод start(...)
в приложении JavaFX в качестве замены для метода main(...)
в «традиционном» Java-приложении с пониманием, которое оно вызывается в потоке приложения FX.
Моя рекомендация заключается в том, что подкласс Application
должен быть как можно более минимальным; он должен просто делегировать что-то еще, чтобы фактически создать пользовательский интерфейс, а затем просто разместить его на первом этапе и показать его. Включите метод main
, который ничего не делает, кроме вызова launch(...)
в качестве резервной копии для JVM, не поддерживающих JavaFX. У вас должен быть только один экземпляр одного подкласса Application
, присутствующего в любой JVM. Таким образом, ваш подкласс Application
не имеет членов класса для установки, и поэтому описанные вами проблемы просто не возникают.
Если вы используете FXML, это на самом деле довольно естественно: метод start(...)
по существу просто делегирует пару FXML-контроллеров для выполнения реальной работы. Если вы не используете FXML, создайте отдельный класс для выполнения фактического макета и т. Д. И делегируйте его. См. this related question, который получает ту же идею.
Заметим также, что ваше заявление
метод наследуется запуска(), хотя это публично, должно быть названо внутри этого производного класса
не совсем точно, так как есть overloaded form of the launch(...)
method, в котором вы можете указать подкласс приложения. Так что, если вам действительно нужно, вы можете просто создать заглушку для запуска FX инструментарий:
public class FXStarter extends Application {
@Override
public void start(Stage primaryStage) {
// no-op
}
}
Теперь вы можете сделать:
public class MyRegularApplication {
public static void main(String[] args) {
// start FX toolkit:
new Thread(() -> Application.launch(FXStarter.class)).start();
// other stuff here...
}
}
Обратите внимание, что launch
не возвращается, пока FX инструментарий не отключит , поэтому необходимо поместить этот вызов в другой поток. Это потенциально создает условия гонки, где вы можете попробовать сделать что-то нуждаясь в FX инструментарий, прежде чем launch(...)
фактически инициализируются его, так что вы, вероятно, следует остерегаться, что:
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
}
}
, а затем
public class MyRegularApplication {
public static void main(String[] args) throws InterruptedException {
// start FX toolkit:
new Thread(() -> Application.launch(FXStarter.class)).start();
FXStarter.awaitFXToolkit();
// other stuff here...
}
}
SSCCE (Я просто использовал внутренние классы для всего, так что это удобно работать, но в реальной жизни это было бы автономные классы):
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class BackgroundProcessDrivenApp {
public static void main(String[] args) throws InterruptedException {
Platform.setImplicitExit(false);
new Thread(() -> Application.launch(FXStarter.class)).start();
FXStarter.awaitFXToolkit();
new MockProcessor().doStuff() ;
}
public static class FXStarter extends Application {
private static final CountDownLatch latch = new CountDownLatch(1);
@Override
public void init() {
latch.countDown();
}
public static void awaitFXToolkit() throws InterruptedException {
latch.await();
}
@Override
public void start(Stage primaryStage) { }
}
public static class MockProcessor {
private final int numEvents = 10 ;
public void doStuff() {
Random rng = new Random();
try {
for (int event = 1 ; event <= numEvents; event++) {
// just sleep to mimic waiting for background service...
Thread.sleep(rng.nextInt(5000) + 5000);
String message = "Event " + event + " occurred" ;
Platform.runLater(() -> new Messager(message).showMessageInNewWindow());
}
} catch (InterruptedException exc) {
Thread.currentThread().interrupt();
} finally {
Platform.setImplicitExit(true);
}
}
}
public static class Messager {
private final String message ;
public Messager(String message) {
this.message = message ;
}
public void showMessageInNewWindow() {
Stage stage = new Stage();
Label label = new Label(message);
Button button = new Button("OK");
button.setOnAction(e -> stage.hide());
VBox root = new VBox(10, label, button);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 350, 120);
stage.setScene(scene);
stage.setAlwaysOnTop(true);
stage.show();
}
}
}
Возможно, вы могли бы указать более конкретный пример того, где это создает трудности, возможно, кто-то сможет предложить соответствующий рефакторинг вашего кода. –
ОК, но этот класс выглядит очень маловероятным кандидатом для подкласса 'Application'. Разве это не просто отдельный класс, который используется приложением? Подкласс 'Application' представляет собой реальное приложение в целом. –
Да, это должен быть отдельный класс с несколькими экземплярами. Мое намерение задать этот вопрос было именно тем, что я не хочу, чтобы приложение использовало мой класс. Я хочу, чтобы мое приложение Java использовало JavaFX. Мой основной класс в другом месте и его изменение в подклассу класса приложений javafx полностью исключается. –