Skip to main content

Using JavaFX 2.0 inside Swing applications

Posted by ixmal on June 2, 2011 at 5:47 AM PDT

JavaFX 2.0 Beta is out on May 26, 2011. There are many blogs posted about it already, http://fxexperience.com/ is the primary one, and I don't want to post yet another overview of what features are available. Instead, I would like to take a deeper tour into one particular component called JFXPanel


What is JFXPanel?



JavaFX 1.x provided a way to insert existing Swing components into FX scenes. It was neither declared a feature, nor it was supported perfectly. With the introduction of Prism, a new graphics stack for FX, which is completely untied from AWT and Swing, the embedding has become just impossible. However, it makes much more sense to use FX content in Swing, because there are so many existing Swing applications and so many exciting FX features. The good news is that using FX in Swing is now possible and, which is more important, officially supported via public API: javafx.embed.swing.JFXPanel.


Sample code



JFXPanel JavaDoc includes a short template how it should be used. Here it is:


     public class Test {

         private static void initAndShowGUI() {
             // This method is invoked on Swing thread
             JFrame frame = new JFrame("FX");
             final JFXPanel fxPanel = new JFXPanel();
             frame.add(fxPanel);
             frame.setVisible(true);

             Platform.runLater(new Runnable() {
                 @Override
                 public void run() {
                     initFX(fxPanel);
                 }
             });
         }

         private static void initFX(JFXPanel fxPanel) {
             // This method is invoked on JavaFX thread
             Scene scene = createScene();
             fxPanel.setScene(scene);
         }

         public static void main(String[] args) {
             SwingUtilities.invokeLater(new Runnable() {
                 @Override
                 public void run() {
                     initAndShowGUI();
                 }
             });
         }
    }

Let's examine this sample. The main() method uses a standard pattern to move initialization to Event Dispatch Thread using SwingUtilities.invokeLater(). JFXPanel creation is not interesting as well: it's a regular JComponent and therefore must be created on EDT only. If no FX content is attached to the panel, it's painted as a rectangle filled with a default background color, which is probably not what we need, so let's move to the most important part.



JavaFX 2.0 has its own threading model, which is described, for example, here: http://weblogs.java.net/blog/opinali/archive/2011/05/28/javafx-20-beta-f.... Yes, JavaFX 2.0 is a multi-threaded library, but application developers should only care about one thread called "JavaFX Application Thread". This is the thread where all events are handled, all animations are performed and all scenegraph nodes should be created and mutated. To run your code on this thread, Platform.runLater() should be used, and we see this call in our code.



JFXPanel is a container for an FX scene. In a regular JavaFX application, developers create Scene objects and attach them to top-level windows with Stage.setScene() calls. JFXPanel provides exactly the same API: its setScene() method should be used to attach a Scene. This method is designed carefully, so it can be used both from EDT and FX application thread.


Events and painting

Fine, we've just created an FX scene and attached it to a JFXPanel. What's next? How can I paint FX content in my JFXPanel? How input events are handled? If you ask questions like these, I have a good news: you don't need to bother about all that! Input events are forwarded from Swing to FX automatically and then can be handled using exactly the same API as any other FX events. When FX content is changed, it's painted into Swing automatically. What you do need to care about is threading: every time you need to operate with Swing components, SwingUtilities.invokeLater() is your friend, while Platform.runLater() is the way to work with FX objects. That's it!



JFXPanel can also calculate its preferred size based on FX content. If you create the scene with


    Scene scene = new Scene(parent, 200, 200);

or


    Scene scene = new Scene(group);
    group.getChildren().add(new Rectangle(50, 50, 150, 150));

the panel preferred size will be 200 x 200 pixels. Of course, your app will benefit from this only if the layout manager used respects component's preferred sizes.



Ah, yes, there's one more JFXPanel feature to mention. It's completely lightweight. What does it mean? See the screenshot where transparent JFXPanel is placed over Swing button:





Related Topics >>

Comments

In the recent JavaFX 2.0 builds we have simplified the way ...

In the recent JavaFX 2.0 builds we have simplified the way how FX runtime is initialized when embedding into Swing. Now the minimal test is as simple as



<pre>
public class Test {

    private static void initAndShowGUI() {
        JFrame frame = ...
        JFXPanel fxPanel = new JFXPanel();
        frame.add(fxPanel);
        frame.setVisible(true);

        Platform.runLater(new Runnable() {
            public void run() {
                Scene scene = ...
                fxPanel.setScene(scene);
            }
        });
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                initAndShowGUI();
            }
        });
    }
}
</pre>

<p>Actual Hello world example for JFX in Swing: you need to ...

Actual Hello world example for JFX in Swing: you need to start a dummy JFX application to initialize the JFX toolkit.

Edit: ah, I already got bitten by the multi threading. I needed to wait until the JFX scene is set before packing the frame so the size is correct (could have used another invokeLater instead of the latch).

import java.awt.EventQueue;
import java.util.concurrent.CountDownLatch;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.effect.Reflection;
import javafx.scene.layout.BorderPane;
import javafx.scene.text.Font;
import javafx.stage.Stage;

import javax.swing.JFrame;

public class JFXinSwing {

public static class JFXInit extends Application {
@Override
public void start(Stage stage) throws Exception {
// for comparison
stage.setTitle("Test JFX");
stage.setScene(createScene());
stage.setVisible(true);
}

public static void initializeJavaFX() {
Application.launch(new String[0]);
}
}

private static Scene createScene() {
Label label = new Label("Hello world!");
label.setFont(new Font(24));
label.setEffect(new Reflection());

BorderPane pane = new BorderPane();
pane.setCenter(label);
Scene scene = new Scene(pane);
return scene;
}

public static void main(String[] args) {
JFXInit.initializeJavaFX();

EventQueue.invokeLater(new Runnable() {
public void run() {
final CountDownLatch init = new CountDownLatch(1);
final JFXPanel panel = new JFXPanel();
Platform.runLater(new Runnable() {
@Override
public void run() {
panel.setScene(createScene());
init.countDown();
}
});

try {
init.await();
}
catch (InterruptedException e) {
e.printStackTrace();
}

JFrame frame = new JFrame("Test JFXPanel");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(panel);
frame.pack();

frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}

When I run this code only the

When I run this code only the "TestJFX" scene is vidible. Are you sure that you didnt confuse the JFXScene for the JFrame? Thanks, I am trying to use some JFX in a Swing app and knowing if your code is in fact necassary would be a big help.

Thanks again!

<p>Walter, don't quite understand what you'r ...

Walter, don't quite understand what you'r saying

- why the Application.launch?

- can't we do the pack in the platform runnable?

Cheers, Jeanette

 

Edit: answering the second - not without an additional invoke ;-) BTW: I _hate_ this editor .... in which century are we living?

Edit2: and found the first as well // for comparison :-)

 

<p>1) The comparison was an added benefit. Originally the ...

1) The comparison was an added benefit. Originally the Application#start was empty but I needed to call Application#launch to initialize the JFX toolkit (or you get an exception). Artem Ananiev/ixmal answered above that that isn't needed anymore in a next build (no idea if that's available already).
2) Yeah, a Swing.invokeLater inside the Platform.runLater is probably would be more obvious. I was just happy it with the latch worked and lunch break was over before I could even start to worry about half visible reflections!