68

My application is Swing-based. I would like to introduce JavaFX and configure it to render a Scene on a secondary display. I could use a JFrame to hold a JFXPanel which could hold a JFXPanel but I would like to achieve this with JavaFX API.

Subclassing com.sun.glass.ui.Application and using Application.launch(this) is not an option because the invoking thread would be blocked.

When instantiating a Stage from Swing EDT, the error I get is:

java.lang.IllegalStateException: Toolkit not initialized

Any pointers?


EDIT: Conclusions

Problem: Non-trivial Swing GUI application needs to run JavaFX components. Application's startup process initializes the GUI after starting up a dependent service layer.

Solutions

Subclass JavaFX Application class and run it in a separate thread e.g.:

public class JavaFXInitializer extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        // JavaFX should be initialized
        someGlobalVar.setInitialized(true);
    }
}

Sidenote: Because Application.launch() method takes a Class<? extends Application> as an argument, one has to use a global variable to signal JavaFX environment has been initialized.

Alternative approach: instantiate JFXPanel in 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();

By using this approach the calling thread will wait until JavaFX environment is set up.

Pick any solution you see fit. I went with the second one because it doesn't need a global variable to signal the initialization of JavaFX environment and also doesn't waste a thread.

Dan Vulpe
  • 1,316
  • 1
  • 11
  • 13
  • N.B.: recommended solution to work with JavaFX from Swing application is to create JFXPanel and pass JavaFX scene to jfxPanel.setScene() method. See http://docs.oracle.com/javafx/2/api/javafx/embed/swing/JFXPanel.html – Sergey Grinev Jul 02 '12 at 14:55
  • @SergeyGrinev: Well, but as some Components have issues being used within JFXPanel, another way to work wit JavaFX 2 from Swing is highly appreciated. – dajood Jul 26 '12 at 15:16
  • Hi. Which components have issues? – Sergey Grinev Jul 26 '12 at 15:57
  • 1
    The HTMLEditor Component doesn't accept the Enter-Key when inside of a JFXPanel - that's in my eyes a showstopping issue for an editor component. See also here: http://javafx-jira.kenai.com/browse/RT-20887 – dajood Jul 26 '12 at 18:19
  • Here's a simplification using Java 8 that turns it into a one-liner: SwingUtilities.invokeAndWait(() -> new JFXPanel()); – NateW Oct 03 '18 at 00:12

8 Answers8

24

Found a solution. If I just create a JFXPanel from Swing EDT before invoking JavaFX Platform.runLater it works. I don't know how reliable this solution is, I might choose JFXPanel and JFrame if turns out to be unstable.

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();
                    }
                });
            }
        });
    }
}
Dan Vulpe
  • 1,316
  • 1
  • 11
  • 13
23

Since JavaFX 9, you can run JavaFX application without extending Application class, by calling Platform.startup():

Platform.startup(() ->
{
    // This block will be executed on JavaFX Thread
});

This method starts the JavaFX runtime.

madx
  • 6,723
  • 4
  • 55
  • 59
Eng.Fouad
  • 115,165
  • 71
  • 313
  • 417
21

The only way to work with JavaFX is to subclass Application or use JFXPanel, exactly because they prepare env and toolkit.

Blocking thread can be solved by using new Thread(...).

Although I suggest to use JFXPanel if you are using JavaFX in the same VM as Swing/AWT, you can find more details here: Is it OK to use AWT with JavaFx?

Community
  • 1
  • 1
Sergey Grinev
  • 34,078
  • 10
  • 128
  • 141
  • I was avoiding new Thread(...) stuff because my application is Swing based. Thanks for the hint. – Dan Vulpe Jun 30 '12 at 21:12
  • My application has a specific startup routine. The GUI initialisation takes place in a later stage, invoked from the main thread. I was looking into starting JavaFX environment and getting a notification when it's done. Using Application.launch it's not a solution for me because it blocks the calling thread. – Dan Vulpe Jul 01 '12 at 05:09
  • I still don't see any conflicts with using Thread. And if you wanna stick with this approach test in on Mac before production. – Sergey Grinev Jul 01 '12 at 09:20
  • I develop mainly on Mac and this approach works fine as long as the main thread doesn't exit. I'm not saying there is a conflict by using a new Thread, it's just that I don't prefer blocking a thread. In my opinion both solutions are workarounds for an api call that's missing in the JavaFX environment/toolkit. – Dan Vulpe Jul 01 '12 at 11:26
  • Summed everything up in the first post. Thanks for your feedback. – Dan Vulpe Jul 02 '12 at 12:24
8

I checked the source code and this is to initialize it

com.sun.javafx.application.PlatformImpl.startup(()->{});

and to exit it

com.sun.javafx.application.PlatformImpl.exit();
user28775
  • 129
  • 1
  • 3
  • 6
    Calling internal `com.sun` classes like this does not work on Java 9+ because the package is not exported. – Thunderforge Jul 09 '18 at 02:54
  • I was able to use the approach in this answer successfully when building and executing tests with JDK 11, specifically Azul Community JDK 11.0.11+9 x86_64, and OpenJFX 16, on Windows 10 64-bit. I put the call to `PlatformImpl.startup` in the `@BeforeClass setUpOnce` method, and the call to `PlatformImpl.exit` in the `tearDownOnce` method in my JUnit 4 test suite. These `com.sun` method calls result in only a warning at runtime – MikeOnline May 04 '21 at 06:27
4

I used following when creating unittests for testing javaFX tableview updates

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

        }
    }

even though this post is not test related, then it helped me to get my unittest to work

  • without the BeforeClass initToolkit, then the instantiation of TableView in the unittest would yield a message of missing toolkit
serup
  • 3,676
  • 2
  • 30
  • 34
  • Thanks for this... I'm also trying to figure out JavaFX and testing. But most non-trivial JavaFX operations produce "IllegalStateException: This operation is permitted on the event thread only;". And running inside a `Plaform.runLater()` `Runnable` doesn't work: with Swing it was always necessary to use `EventQueue.invokeAndWait()` - otherwise the test would end before the Runnable ran. Isn't it the case that you pretty much have to use sthg like TestFX for JavaFX testing? – mike rodent Oct 18 '16 at 18:53
  • PS of course you can use latches to achieve this "waiting for a Runnable to end"... but it's hardly elegant. – mike rodent Oct 18 '16 at 19:00
  • @mikerodent, I agree it is not elegant to use latches to wait for a utility to be present, however when it comes to being dependent on external entities, then this is perhaps not as clumsy as it could be – serup Feb 20 '17 at 10:30
4

There's also way to initialize toolkit explicitly, by calling: com.sun.javafx.application.PlatformImpl#startup(Runnable)

Little bit hacky, due to using *Impl, but is useful, if you don't want to use Application or JXFPanel for some reason.

re-posting myself from this post

Community
  • 1
  • 1
krzychek
  • 300
  • 3
  • 9
  • 1
    I want to give this answer a standing ovation! I am writing an application with the intent of having two UI the first is in FX, the second non-graphic. My model uses FX collections. Without this you can't use the collections and not have a graphics element. – Bday Feb 14 '17 at 19:26
  • This also solves a race condition when starting the model before the graphics. So thanks to no end! – Bday Feb 14 '17 at 19:33
  • Removes the needs to subclass stuff and runs before the UI Thread. Clearly what I needed to preprare before the GUI-Thread (please google! Remember me...). Thanks a bunch! – geisterfurz007 Jul 12 '17 at 12:09
  • 2
    Calling internal `com.sun` classes like this does not work on Java 9+ because the package is not exported. – Thunderforge Jul 09 '18 at 02:54
0
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");
}

This is my solution. The class is named Main and implements Runnable. Method startup(Runnable r) is the key.

Jack Lin
  • 41
  • 3
0

Using Jack Lin’s answer, I found that it fired off the run() twice. With a few modifications that also made the answer more concise, I offer the following;

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
    }
}
Ray2U
  • 31
  • 2