JavaFX 2.1: инструментарий не инициализирован
Мое приложение на основе Swing. Я хотел бы представить JavaFX и настроить его для визуализации сцены на дополнительном дисплее. Я мог бы использовать JFrame для хранения JFXPanel, которая могла бы содержать JFXPanel, но я хотел бы добиться этого с помощью JavaFX API.
Создание подклассов com.sun.glass.ui.Application и использование Application.launch(this) не вариант, потому что вызывающий поток будет заблокирован.
Когда я создаю экземпляр Stage из Swing EDT, я получаю следующую ошибку:
java.lang.IllegalStateException: Toolkit not initialized
Есть указатели?
РЕДАКТИРОВАТЬ: Выводы
Проблема: нетривиальное приложение Swing GUI должно запускать компоненты JavaFX. Процесс запуска приложения инициализирует графический интерфейс после запуска зависимого сервисного уровня.
Решения
Подкласс JavaFX Application class и запуск его в отдельном потоке, например:
public class JavaFXInitializer extends Application {
@Override
public void start(Stage stage) throws Exception {
// JavaFX should be initialized
someGlobalVar.setInitialized(true);
}
}
Sidenote: потому что метод Application.launch() принимает Class<? extends Application>
в качестве аргумента необходимо использовать глобальную переменную, чтобы указать, что среда JavaFX инициализирована.
Альтернативный подход: создать экземпляр JFXPanel в Swing Event Dispatcher Thread:
final CountDownLatch latch = new CountDownLatch(1);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new JFXPanel(); // initializes JavaFX environment
latch.countDown();
}
});
latch.await();
Используя этот подход, вызывающий поток будет ждать, пока не будет настроена среда JavaFX.
Выберите любое решение, которое считаете нужным. Я пошел со вторым, потому что ему не нужна глобальная переменная для сигнализации инициализации среды JavaFX, а также не тратится впустую поток.
8 ответов
Нашел решение. Если я просто создаю JFXPanel из Swing EDT до вызова JavaFX Platform.runLater, это работает. Я не знаю, насколько надежно это решение, я мог бы выбрать JFXPanel и JFrame, если они окажутся нестабильными.
public class BootJavaFX {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new JFXPanel(); // this will prepare JavaFX toolkit and environment
Platform.runLater(new Runnable() {
@Override
public void run() {
StageBuilder.create()
.scene(SceneBuilder.create()
.width(320)
.height(240)
.root(LabelBuilder.create()
.font(Font.font("Arial", 54))
.text("JavaFX")
.build())
.build())
.onCloseRequest(new EventHandler<WindowEvent>() {
@Override
public void handle(WindowEvent windowEvent) {
System.exit(0);
}
})
.build()
.show();
}
});
}
});
}
}
Начиная с JavaFX 9, вы можете запускать приложение JavaFX без расширения Application
класс, позвонив Platform.startup()
:
Platform.startup(() ->
{
// This block will be executed on JavaFX Thread
}
Этот метод запускает среду выполнения JavaFX.
Единственный способ работать с JavaFX - это создать подкласс Application или использовать JFXPanel, именно потому, что они готовят env и toolkit.
Блокировка потока может быть решена с помощью new Thread(...)
,
Хотя я предлагаю использовать JFXPanel, если вы используете JavaFX на той же виртуальной машине, что и Swing/AWT, вы можете найти более подробную информацию здесь: Можно ли использовать AWT с JavaFx?
Я проверил исходный код, и это для его инициализации
com.sun.javafx.application.PlatformImpl.startup(()->{});
и выйти из него
com.sun.javafx.application.PlatformImpl.exit();
Я использовал следующее при создании юнит-тестов для тестирования обновлений javaFX tableview
public class testingTableView {
@BeforeClass
public static void initToolkit() throws InterruptedException
{
final CountDownLatch latch = new CountDownLatch(1);
SwingUtilities.invokeLater(() -> {
new JFXPanel(); // initializes JavaFX environment
latch.countDown();
});
if (!latch.await(5L, TimeUnit.SECONDS))
throw new ExceptionInInitializerError();
}
@Test
public void updateTableView() throws Exception {
TableView<yourclassDefiningEntries> yourTable = new TableView<>();
.... do your testing stuff
}
}
хотя этот пост не имеет отношения к тестам, он помог мне заставить мой unittest работать
- без initCoolkit BeforeClass создание экземпляра TableView в unittest приведет к сообщению об отсутствии инструментария
Существует также способ явной инициализации инструментария путем вызова:com.sun.javafx.application.PlatformImpl#startup(Runnable)
Немного хак, из-за использования *Impl, но полезно, если вы не хотите использовать Application
или же JXFPanel
по какой-то причине.
повторная публикация себя из этого поста
private static Thread thread;
public static void main(String[] args) {
Main main = new Main();
startup(main);
thread = new Thread(main);
thread.start();
}
public static void startup(Runnable r) {
com.sun.javafx.application.PlatformImpl.startup(r);
}
@Override
public void run() {
SoundPlayer.play("BelievexBelieve.mp3");
}
Это моё решение. Класс называется Main и реализует Runnable. метод startup(Runnable r)
это ключ.
Используя ответ Джека Линя, я обнаружил, что он дважды запускал run(). С некоторыми изменениями, которые также сделали ответ более кратким, я предлагаю следующее;
import com.sun.javafx.application.PlatformImpl;
public class MyFxTest implements Runnable {
public static void main(String[] args) {
MyFxTest main = new MyFxTest();
PlatformImpl.startup((Runnable) main);
}
@Override
public void run() {
// do your testing;
System.out.println("Here 'tis");
System.exit(0); // Optional
}
}