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
    }
}
Другие вопросы по тегам