用纯Java代码调用JavaFX的功能

分类: JavaFX技巧, JavaFX编程.
标签: , , , ,

在JavaFX 1.0发布之后,本人撰写的文章JavaFX和Java之间的互操作性被各网站转载。文中总结了3种从Java调用JavaFX的方法。这三种方法分别为:

1. 用ScriptEngineManager类。这是基于JSR-223规范的java脚本API( scripting API)。可以在java程序中运行一些脚本,如 JavaFX script, javascript等。
2. 通过JavaFX reflection API。这是JavaFX提供的反射API,几乎可以调用所有的JavaFX类。
3. 先用JavaFX类实现一个Java的interface,然后Java程序可以通过这个interface来调用JavaFX的功能。这中间的interface起了个桥梁的作用。

第三种方法应该是比较“正规”而且“优美”的调用方式。但是也有一点不足:程序代码的入口必须从JavaFX启动。这是因为JavaFX程序比较容易生成JavaFX类的实例,从而可以供Java方使用。可是我们有时会碰到一些特定的情况,需要从Java端启动代码。例如,如果你已经有了一个较完整的Java程序,你需要用调用JavaFX的某些功能,这时候较好的方法是把Java作为程序的入口。为了解决这个问题,我把第2和第3种方法作了融合,大家可以看看下面的例子。

假定我们需要从Java中调用JavaFX的图表(charting)功能。我们首先用JavaFX reflection API生成JavaFX类的实例。然后我们再通过java的interface来使用它。因此,我们先定义一个Java的interface:

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

下一步是创建JavaFX类MyChart来实现这个interface:

/*
 * MyChart.fx
 *
 * @author Henry Zhang     http://www.javafxblogs.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
        ]
      }
    }
  }
}

最后就是从java类JavaTest中调用图表功能:

/*
 * JavaTest.java
 * @author Henry Zhang    http://www.javafxblogs.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();
  }
}

在代码中,这3句是创建JavaFX类javatest.MyChart实例,

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

而一下这句则是把JavaFX实例转化为Java可用的对象:

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

如果你用的是NetBeans IDE, 你可以在项目属性中把javatest.JavaTest类设为主类(Main class)(即启动类)。编译之后会生成一个javatest.jar文件。程序运行的结果如图:

Java PieChart via JavaFX

在命令行中可以采用以下方式:

   javafx -jar javatest.jar

实际上,可以用纯Java的方式来启动程序,只要把JavaFX的运行环境带上即可,如:

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

因为JavaFX需要的jar文件很多,因此这种“最纯”的java方法使用起来比较麻烦。我觉得还是用javafx命令简洁些, 而且javafx就是上述java命令的封装而已。

如果有问题,请留言讨论。

本文的英文译文同步发表于:Calling JavaFX Classes from Pure Java Code.

评论 (1) 2009年06月21日

JavaFX和Java之间的互操作性

分类: JAVAFX技术.
标签: , ,

JavaFX官方博客上读了一篇文章,是讨论从java代码中调用JavaFX类的方法。现在的情况是,JavaFX可以调用Java的类,基本没有什么限制,而反过来,Java却不可以随便调用JavaFX的类。这点可以从JavaFX项目的编译过程看出原因。以NetBeans为例,Build的过程是先编译Java代码(javac),然后才是JavaFX代码(javafxc),这样一来,java代码不知道有javafx类,而javafx类却可以”看见”java类。搜索一下我们可以发现,很多程序员都在寻找各种从java中调用JavaFX类的方法。有一篇有趣的(英文)文章是介绍如何通过反向工程来分析JavaFX类的结构。就连那篇JavaFX官网上的文章,也采用了非标准的API来实现这一目的,而且也”保证”这种方法肯定会在下一版本中失效。

那么我们到底需不需要java和javafx之间的这种互操作性呢?我觉得这种互操作性是很有必要的。如果两者可以近似于可以混用的程度,从长远上看,JavaFX可以有更大的生命力。试想一下运用MVC的设计模式(Model-View-Controller),我们可以用java和javafx结合在一起开发应用:用java来写”M”和”C”两部分,用javaFX来写”V”部分,这将是非常有趣的一件事情。

目前,有几种”标准”的方法来从Java调用JavaFX。
1) 使用ScirptEngineManager类,的文章提到,我们可以这样做:

package calc;
import java.io.InputStreamReader;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class CalculatorLauncher {
public static void main(String[] args) {
 try {
 ScriptEngineManager manager=new ScriptEngineManager();
 ScriptEngine engine = manager.getEngineByExtension("fx");
 InputStreamReader reader = new InputStreamReader
 (CalculatorLauncher.class.getResourceAsStream("Calculator.fx"));
 engine.eval(reader);
   } catch (ScriptException ex) {
  }
 }
}

但是,这种方法其实没有多大意义,因为它就是象System.exec(”calc”)那样做个系统调用而已。我觉得还不如用System.exec(”javafx Calculator.fx”)更加直接一些。

2) 采用Java Reflection来解析JavaFX的bytecode,得到各个method或属性,然后进行各种调用。原理上这是可行的。但是由于reflection非常复杂,使得实用性大打折扣,同时,代码也没有什么可读性了。

3)第三种方法是定义一个java的interface,然后在JavaFX中实现这个 interface。例如:

public interface JavaInterface
{ ... }

在 MyJavaFXClass.fx中, 可以这样写:

public class MyJavaFXClass extends JavaInterface
{ ... }

在java代码中,只需按照interface来调用JavaFX对象即可。这种方法可以解决大部分互操作性的问题。唯一的麻烦就是必需定义一大堆interface,但是这是我目前位置发现的一种最好的解决形式。
JavaFX现在是刚发布的第一版,所以我们无需对它苛求太多了。不过我还是希望javaFX的设计者在下一版本中认真考虑这个问题。

评论 (1) 2009年01月15日