Ky -
Ky -

Reputation: 32093

How do I put something on an FX thread?

I'm new to JavaFX (trying to move from Swing), and am trying to make a very basic window first. However, I keep getting the following runtime exception:

Exception in thread "main" java.lang.IllegalStateException: Not on FX application thread; currentThread = main
    at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:210)
    at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:393)
    at javafx.scene.Scene.<init>(Scene.java:374)
    at javafx.scene.Scene.<init>(Scene.java:232)
    at bht.tools.util.upd.TestDialog.initGUI(TestDialog.java:39)
    at bht.tools.util.upd.TestDialog.<init>(TestDialog.java:24)
    at bht.tools.util.upd.TestDialog.main(TestDialog.java:52)
package bht.tools.util.upd;

import java.awt.Window;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.GridPane;
import javax.swing.JDialog;

/**
 * TestDialog is copyright Blue Husky Programming ©2014 GPLv3 <hr/>
 * 
 * @author Kyli of Blue Husky Programming
 * @version 1.0.0
 *      - 2014-09-30 (1.0.0) - Kyli created TestDialog
 * @since 2014-09-30
 */
public class TestDialog extends JDialog
{
    public TestDialog(Window owner)
    {
        super(owner);
        initGUI();
    }

    private JFXPanel holder;
    private Scene interior;
    private ProgressBar progressBar;
    private GridPane root;
    private void initGUI()
    {
        {
            holder = new JFXPanel();
            setContentPane(holder);
            root = new GridPane();
            interior = holder.getScene();
            if (interior == null)
                holder.setScene(interior = new Scene(root));
            interior.setRoot(root);
        }
        {
            progressBar = new ProgressBar();
            progressBar.setProgress(-1);
            root.add(progressBar, 0, 1);
        }
        pack();
    }

    public static void main(String[] args)
    {
        new TestDialog(null).setVisible(true);
    }
}

However, looking at the JFXPanel source code, the constructor calls its initFX() method, which initializes the FX application thread. Why, then, am I getting this error, and how do I fix it?

Upvotes: 2

Views: 1794

Answers (2)

Warren Nocos
Warren Nocos

Reputation: 1282

All JavaFX Controls must be initialized or modified within the FX Application thread - this is by calling Platform.runLater(() -> { //your JavaFX code });

JFXPanel could be initialized inside the SwingUtility thread, but setting a JavaFX scene on the JFXPanel must be done within the FX Application thread. Example:

//still on Swing thread
JFXPanel panel = new JFXPanel();
//now setting the scene to be embedded on the JFXPanel
//this must be done inside the FX Application thread
Platform.runLater(() -> {
   Group group = new Group();
   group.getChildren.addAll(new Button("Cancel"), new Button("Save"), new Text("Status"));
   panel.setScene(new StackPane(group));
});
//back on the Swing thread
frame.add(panel);

Hope this helps.

Upvotes: 2

James_D
James_D

Reputation: 209358

The JFXPanel initializes the FX Application Thread, so it is now running; however your code is not being executed on that thread. In order to execute code on the FX Application Thread, you can call Platform.runLater(...) which is roughly analogous to SwingUtilities.invokeLater(...) in Swing and AWT applications.

This is taken more or less straight from the tutorial, but for your example code you would do

package bht.tools.util.upd;

import java.awt.Window;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.GridPane;
import javax.swing.JDialog;

/**
 * TestDialog is copyright Blue Husky Programming ©2014 GPLv3 <hr/>
 * 
 * @author Kyli of Blue Husky Programming
 * @version 1.0.0
 *      - 2014-09-30 (1.0.0) - Kyli created TestDialog
 * @since 2014-09-30
 */
public class TestDialog extends JDialog
{
    public TestDialog(Window owner)
    {
        super(owner);
        initGUI();
    }

    private JFXPanel holder;
    private Scene interior;
    private ProgressBar progressBar;
    private GridPane root;
    private void initGUI()
    {
        holder = new JFXPanel();
        setContentPane(holder);
        Platform.runLater( () -> initJFXPanel(holder) );
        pack();
    }

    private void initJFXPanel(JFXPanel holder) {
        root = new GridPane();
        interior = holder.getScene();
        if (interior == null)
            holder.setScene(interior = new Scene(root));
        interior.setRoot(root);
        progressBar = new ProgressBar();
        progressBar.setProgress(-1);
        root.add(progressBar, 0, 1);
    }

    public static void main(String[] args)
    {
        new TestDialog(null).setVisible(true);
    }
}

Note that you're breaking Swing's threading rules too, though I think (it's been a while now since I programmed using Swing) that Swing has been bullet-proofed to ensure code like that works. But your main method should really be

public static void main(String[] args)
{
    SwingUtilities.invokeLater(() -> new TestDialog(null).setVisible(true));
}

Upvotes: 2

Related Questions