FX Experience Has Gone Read-Only

I've been maintaining FX Experience for a really long time now, and I love hearing from people who enjoy my weekly links roundup. One thing I've noticed recently is that maintaining two sites (FX Experience and JonathanGiles.net) takes more time than ideal, and splits the audience up. Therefore, FX Experience will become read-only for new blog posts, but weekly posts will continue to be published on JonathanGiles.net. If you follow @FXExperience on Twitter, I suggest you also follow @JonathanGiles. This is not the end - just a consolidation of my online presence to make my life a little easier!

tl;dr: Follow me on Twitter and check for the latest news on JonathanGiles.net.

With JavaFX 2.0.2, we’ve included support for interop with SWT in the same way that we support interop with Swing. That is, you can embed JavaFX within your SWT applications! Although e(fx)clipse has been doing this for a little while by embedding FX -> Swing -> SWT, you can now skip the intermediate embedding into Swing and just go straight to SWT. Because FX and SWT share the same basic threading model, this is really easy to do.

In this code sample, the Table is an SWT table, but the chart is a JavaFX Chart. I create the data model and place it in an ObservableList, and supply this to both the table and the chart. As you edit the table, it ends up also directly manipulating the chart data model, which causes it to animate to the new value.

package swt.demo;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.embed.swt.FXCanvas;
import javafx.scene.Scene;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.util.Pair;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.TableEditor;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.TabItem;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;

import com.sun.javafx.runtime.VersionInfo;

* @author Richard
public class SWTDemo {
   public static void main(String[] args) {
       Display display = new Display();
       Shell shell = new Shell(display);

       // Print version info
       System.out.println("SWT " +  SWT.getPlatform() + " " +  SWT.getVersion());
       String runtimeVersion = VersionInfo.getRuntimeVersion();
       System.out.println("FX " + runtimeVersion);

       shell.setLayout(new FillLayout());

       // Create the fake TabPane guy
       final TabFolder tabFolder = new TabFolder(shell, SWT.NONE);
       TabItem chartTab = new TabItem(tabFolder, SWT.NULL);
       chartTab.setText("JavaFX Chart and SWT Table");
       TabItem browserTab = new TabItem(tabFolder, SWT.NULL);
       browserTab.setText("Web Browser");

       Canvas canvas = new Canvas(tabFolder, SWT.NULL);
       GridLayout layout = new GridLayout(1, true);

       // Create the data (I'll use the Chart's data model for
       // both the chart and the table)
       ObservableList<BarChart.Series> bcData = FXCollections.observableArrayList();
       bcData.add(createSeries(new Pair("2007", new Double(567)), new Pair("2008", new Double(956)), new Pair("2009", new Double(1154))));
       bcData.add(createSeries(new Pair("2007", new Double(1292)), new Pair("2008", new Double(1665)), new Pair("2009", new Double(1927))));
       bcData.add(createSeries(new Pair("2007", new Double(1292)), new Pair("2008", new Double(2559)), new Pair("2009", new Double(2774))));

       Canvas chart = createChart(canvas, bcData);
       chart.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
       Table table = createTable(canvas, bcData);
       table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));

       while (!shell.isDisposed()) {
           if (!display.readAndDispatch()) {

   private static BarChart.Series<String,Number> createSeries(Pair<String,Double>... values) {
       ObservableList<BarChart.Data> series = FXCollections.<BarChart.Data>observableArrayList();
       for (int i=0; i<values.length; i++) {
           series.add(new BarChart.Data(values[i].getKey(), values[i].getValue()));
       return new BarChart.Series(series);

   private static Canvas createChart(Composite shell, ObservableList<BarChart.Series> bcData) {
       final FXCanvas fxPanel = new FXCanvas(shell, SWT.NONE);
       CategoryAxis xAxis = new CategoryAxis();
       xAxis.setCategories(FXCollections.<String>observableArrayList("2007", "2008", "2009"));
       double tickUnit = 1000.0;
       NumberAxis yAxis = new NumberAxis();
       yAxis.setLabel("Units Sold");
       final BarChart chart = new BarChart(xAxis, yAxis, bcData);
       fxPanel.setScene(new Scene(chart));
       return fxPanel;

   private static Table createTable(Composite parent, ObservableList<BarChart.Series> bcData) {
       Table table = new Table (parent, SWT.BORDER | SWT.HIDE_SELECTION);
       String[] titles = {"2007", "2008", "2009"};
       for (int i=0; i<titles.length; i++) {
           TableColumn column = new TableColumn (table, SWT.NONE);
           column.setText(titles [i]);
       for (int i=0; i<bcData.size(); i++) {
           createRow(table, bcData.get(i));
       for (int i=0; i<titles.length; i++) {
           table.getColumn (i).setWidth(400/3);
       return table;

   private static TableItem createRow(Table table, BarChart.Series<String,Number> series) {
       TableItem item = new TableItem (table, SWT.NONE);
       int count = 0;
       for (BarChart.Data<String,Number> data : series.getData()) {
           item.setText(count++, data.getYValue().toString());
       return item;

   private static void createTableEditor (final Table table) {
       final TableEditor editor = new TableEditor (table);
       editor.horizontalAlignment = SWT.LEFT;
       editor.grabHorizontal = true;
       table.addListener (SWT.MouseDown, new Listener () {
           public void handleEvent (Event event) {
               Rectangle clientArea = table.getClientArea ();
               Point pt = new Point (event.x, event.y);
               int index = table.getTopIndex ();
               while (index < table.getItemCount ()) {
                   boolean visible = false;
                   final TableItem item = table.getItem (index);
                   for (int i=0; i<table.getColumnCount (); i++) {
                       Rectangle rect = item.getBounds (i);
                       if (rect.contains (pt)) {
                           final int column = i;
                           final Text text = new Text (table, SWT.NONE);
                           final String oldText = item.getText (i);
                           Listener textListener = new Listener () {
                               public void handleEvent (final Event e) {
                                   switch (e.type) {
                                       case SWT.FocusOut:
                                           item.setText (column, text.getText ());
                                           text.dispose ();
                                       case SWT.Traverse:
                                           switch (e.detail) {
                                               case SWT.TRAVERSE_ESCAPE:
                                                   //FALL THROUGH
                                               case SWT.TRAVERSE_RETURN:
                                                   item.setText (column, text.getText ());
                                                   text.dispose ();
                                                   e.doit = false;
                                       case SWT.Dispose:
                                           if (item.isDisposed() || (text.getText().equals(oldText))) break;
                                           BarChart.Data<String,Number> oldData = ((BarChart.Series<String,Number>)item.getData()).getData().get(column);
                                           try {
                                               double result = Double.parseDouble(text.getText().trim());
                                           } catch (NumberFormatException ex) {
                                               item.setText(column, oldText);
                           text.addListener (SWT.FocusOut, textListener);
                           text.addListener (SWT.Traverse, textListener);
                           text.addListener (SWT.Dispose, textListener);
                           editor.setEditor (text, item, i);
                           text.setText (item.getText (i));
                           text.selectAll ();
                           text.setFocus ();
                       if (!visible && rect.intersects (clientArea)) {
                           visible = true;
                   if (!visible) return;