Sunday, August 31, 2008

Compiled JavaFX in Swing applications - again

Update: This doesn't work in the full release of JavaFX. Please see here for how to do this in Java FX 1.0.

I posted a while ago about problems I was having integrating a compiled JavaFX widget into a Swing application - theoretically it should have been possible, as it's all Java and Swing, but I hit a brick wall that looked like it was due to JavaFX being in early, early alpha/beta stage, so I left it and decided to come back to it when JavaFX was more stable.

Well, the preview SDK for JavaFX is now available, and I had a wet Sunday afternoon to spare, so I tried it again. Of course, as it has been 4 or 5 weeks since I last looked at JavaFX they have completely changed the structure and location of the classes, so it took me a while to get the hang of things again, but once I had done that actually getting a JavaFX widget into a Swing application was trivial really.


The screenshot above shows the Stopwatch example from the JavaFX SDK inside a Swing window - the two controls to the left are Swing controls.

The key to getting JavaFX to operate with Swing is the javafx.ext.swing.Canvas object, which has a getJComponent() method, so all you need to do is place all of your JavaFX content onto a Canvas object, then use the JComponent returned by Canvas.getJComponent() to add the JavaFX widget to any Swing container.

For the example above, the Stopwatch widget example runs in a JavaFX frame with no Canvas, but all we need to do is write a simple JavaFX Canvas object to wrap the Stopwatch, using the code below:

package stopwatch;

import javafx.ext.swing.Canvas;
import javax.swing.JComponent;

public class SWCanvas {
public function getChildComponent() {
var sw = StopwatchWidget{}
var c2:Canvas = Canvas {
content: sw
width: 400
height: 400
visible: true
}

return c2.getJComponent();
}
}

Once we have compiled that, we can use our JavaFX 'SWCanvas' object in our Swing application just like any other Java class, so the simple JFrame example pictured above does just that:

package stopwatch;

import javax.swing.*;
import java.awt.Dimension;
import java.awt.FlowLayout;

public class SwingStopwatch {
public static void main(String[] args) {
Runnable r = new Runnable() {
public void run() {
new SwingStopwatch().runMe();
}
};
SwingUtilities.invokeLater(r);

}

public void runMe() {
JFrame jf = new JFrame("Swing and JavaFX Test");
jf.setPreferredSize(new Dimension(600,400));
jf.getContentPane().setLayout(new FlowLayout());

jf.getContentPane().add(new JButton("Click me"));
jf.getContentPane().add(new JTextField("Type into me"));

// JavaFX widget here!
stopwatch.SWCanvas c1 = new stopwatch.SWCanvas();
jf.getContentPane().add(c1.getChildComponent());

jf.pack();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setVisible(true);
}
}


And that's all there is to it. The trickiest part of the whole thing was getting the classpath for the JavaFX runtime right when running the Swing application (your CLASSPATH should look something like: %JAVAFX_HOME%\lib\javafxgui.jar;%JAVAFX_HOME%\lib\javafxrt.jar;%JAVAFX_HOME%\lib\javafx-swing.jar;%JAVAFX_HOME%\lib\Scenario.jar).

With JavaFX's ability to create impressive animated user interfaces quickly, and with relatively little code, then the ability to add JavaFX widgets to any Swing container could be a great way to improve Swing applications incrementally, rather than rewriting the whole application in JavaFX.

I'm definitely not a JavaFX expert though, so if you want some more ideas of the very impressive things that you can do with JavaFX then check out James Weaver's JavaFX Blog.

Friday, August 01, 2008

JApplet and the Swing EDT

So, everyone knows by now that doing anything relating to Swing controls when you're not running in the Swing Event Dispatch Thread (EDT) is a big no-no. Unfortunately this is very easy to do, as Swing doesn't enforce this rule and 90% of the time your code will run with no visible problems if you don't do it (right up until you release it to a customer, at which point bugs will become immediately visible).

A lot of the time it's reasonable that Swing doesn't force execution onto the EDT, certainly in high-level application classes, although the closer you get to the Swing classes the more sense it makes to actually enforce the rule in some way.

For instance when writing an application it's up to the developer to get on the Swing EDT before you create and display your Swing controls. However there are other situations in which it's obvious that Swing controls are going to be used and so Swing should put you on the EDT by default: when you're using JApplet for example.

I hadn't noticed before now but I was surprised to notice that JApplet.init() is not invoked on the EDT. Try it:

public class TestApplet extends JApplet {
public void init() {
System.out.println("JApplet.init - EDT = " +
SwingUtilities.isEventDispatchThread());
}
}
Now if you're using a JApplet you obviously want to do Swing stuff in your applet, so why not help people out and start them off on the Swing EDT?

I guess the issue with doing it by default is that people who don't know about the EDT rule wouldn't be aware that it is being done for them, so you're just avoiding potential bugs without people learning what is involved in avoiding the bugs, so they may just get into another EDT problem further down the line. That may be true, and Swing is definitely an API not a 'framework', allowing you to write code without any help or guidance on how things should be done correctly along the way, but I still think that you should at least start people off on the right track whenever you can - what they do after that is up to them.