Calling JavaFX Classes from Pure Java Code

Posted: June 21st, 2009 under JavaFX Technology.
Tags: , , , ,

Bookmark and Share

Just after the release of JavaFX 1.0, in one of my posts JavaFX and Java Interoperability, I discussed three possible approaches to invoke JavaFX features from the Java side. These approaches were:

1. The ScriptEngineManager class. It is based on JSR-223, the java scripting API, which allows a java program to call a script(such as JavaFX Script, javascript, perl).
2. The JavaFX reflection API. It can probably call any classes in JavaFX.
3. The JavaFX class implements a Java interface so that a Java program can invoke the JavaFX class via the interface.

The third one seems the most elegant way to call JavaFX from Java. However, there is a drawback: the program should start from the JavaFX side. The reason is that it is simpler to use JavaFX code to instantiate the JavaFX classes which can be passed to Java code. Nevertheless, in some scenario, it would be better to start the program from the java side. For example, if you want to add in some JavaFX features to an existing large java application, it is better to have java code as the entry point. To solve this issue, I am combining the essence of Approach 2 and 3 to create the below example.

Let’s say we want to invoke the latest charting functions of JavaFX 1.2 from the java code. We will first use the JavaFX reflection API to instantiate the JavaFX class. We then use it via its java interface. So we define a Java interface first.

/*
 * JavaInterface.java
 *
 * @author Henry Zhang      http://www.javafxgame.com
 */
package javatest;
public interface JavaInterface {
  public void addData(String name, float data);
  public void showChart();
}

The next step is to create a JavaFX class MyChart to implements this interface:

/*
 * MyChart.fx
 *
 * @author Henry Zhang     http://www.javafxgame.com
 */
package javatest;

import javafx.scene.chart.PieChart;
import javafx.scene.Scene;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.scene.chart.PieChart3D;

public class MyChart extends JavaInterface {
  var chartData :  PieChart.Data[] = [];

  public override function addData( l:String, v: Number):Void {
    var labelString = l;

    var data =  PieChart.Data {
      label : l
      value : v
      action: function() {
        println("{labelString} clicked!");
      }
     } ;

    insert data into chartData;
  }

  public override function showChart() : Void {
    var chart =
      PieChart3D {
        data : chartData
        pieThickness: 25
        pieLabelFont: Font{ size: 9 };
        pieToLabelLineOneLength: 10
        pieToLabelLineTwoLength : 20
        pieLabelVisible: true
        pieValueVisible: true
        translateY: -50
     };

    Stage {
      title: "PieChart Window"
      width: 520
      height: 300
      scene: Scene {
        content: [
          Text {
            font : Font {
                    size : 16
                   }
            x: 200
            y: 20
            content: "Pie Chart"
          },
          chart
        ]
      }
    }
  }
}

The last thing is to write the java main class JavaTest.

/*
 * JavaTest.java
 * @author Henry Zhang    http://www.javafxgame.com
 */
package javatest;

import javafx.reflect.FXClassType;
import javafx.reflect.FXLocal;
import javafx.reflect.FXLocal.Context;
import javafx.reflect.FXLocal.ObjectValue;

public class JavaTest {
  public static void main(String args[]) {
    Context context = FXLocal.getContext();
    FXClassType instance = context.findClass("javatest.MyChart");
    ObjectValue obj = (ObjectValue)instance.newInstance();

    JavaInterface ji = (JavaInterface)obj.asObject();

    String [] labels = {"January", "Febuary", "March", "April"};
    int [] values = { 18, 20, 25, 37 };

    for ( int i=0; i < values.length; i++ ) {
      ji.addData(labels[i], values[i]);
    }

    ji.showChart();
  }
}

There are three lines in the above code to instantiate a JavaFX class via reflection:

    Context context = FXLocal.getContext();
    FXClassType instance = context.findClass("javatest.MyChart");
    ObjectValue obj = (ObjectValue)instance.newInstance();

The next line is to convert the JavaFX instance into a java interface so that it can be used by Java side:

    JavaInterface ji = (JavaInterface)obj.asObject();

If you are using NetBeans IDE, you can set javatest.JavaTest as the main class in your project properties. This makes sure that the entry point of your program is a java class. Build this project you will get a javatest.jar. Running this program produces the below screenshot:

Java PieChart via JavaFX

To run it from the command line, use the below command:

   javafx -jar javatest.jar

Actually, you could do it in the purest java style by including all the JavaFX runtime stuffs, the command would look like this:

 java -Djava.library.path="<path to javafx sdk lib>"
     -classpath "<all javafx sdk jars>" -jar javatest.jar

Since there are many jar files used by the JavaFX, this purest java approach turns out to be very troublesome. I would rather use the javafx command, which is a wrapper of the above java command.

Please leave comments if you have any questions.

This article is cross-posted at Pure Java Code to Call JavaFX Class. The Chinese translation can be found at http://www.javafxblogs.com.

P.S. Stephen Chin has released the JFXtras 0.5 version. In this project, a class SceneToJComponent is provided to load a JavaFX scence as a SWING component. Though it uses the internal method of the Scence class, it can insulate your code from future JavaFX change(i.e. Steve will take care of the changes for you. :) )

Bookmark and Share

4 Comments

  1. Nice app! It runs well, but I do receive the following message for each time I hover over part of the chart:

    WARNING * WARNING * WARNING * WARNING * WARNING
    An attempt has been made to add node to a new group without
    first removing it from its current group. See the class
    documentation for javafx.scene.Node for further information.
    This request will be granted temporarily but it will
    be refused in the future. Please change your code now.
    node=Path oldgroup=Group newgroup=Group
    Stack trace follows.

    James,

    I think this is a bug for JavaFX 1.2 .

    henry

    Comment by James — June 22, 2009 @ 2:22 pm

  2. Where are the javafx.reflect classes located. I cannot find them in the 1.2 sdk

    hi Paul,

    You can find it here:

    http://java.sun.com/javafx/1.2/docs/api/

    check the menu on the left. thanks.

    rgds,

    Henry

    Comment by Paul F Fraser — July 19, 2009 @ 10:59 pm

  3. Your code is great !!!.Working very well,thanks for sharing it!

    Comment by Tharindu Madumal Rajarathna — August 5, 2009 @ 2:03 am

  4. @james, @henry, the warning message is indeed a bug in JavaFX 1.2. A full explanation of the warning is here:

    http://stuartmarks.wordpress.com/2009/10/12/that-infernal-scene-graph-warning-message/

    Some internal code in the charts library tripped over scene graph structure enforcement code we added in 1.2, but we didn’t have time to fix it all up. It’s just warning though so it doesn’t affect the behavior. Actually we might have fixed this in 1.2.1 which was pushed out a few weeks ago.

    @ Stuart, thanks for your feedback and you explanation on it. I thought it was a bug too, just glad to see you confirmed it from the product internal.

    Comment by Stuart Marks — October 13, 2009 @ 1:33 am

RSS feed for comments on this post.