Do you speak Java?

piątek, 3 lipca 2009

Beauty and the beast: JavaFX 1.2 in Netbeans Platform 6.7

During last 2 months my and my colleagues from my company were developing application in JavaFX. During this time we have created same really nice working and looking components:
  • Interactive and animating graph with zooming functionality,
  • Editable label with noticing that text inside it was changed external,
  • table containing those editable label (JavaFX itself or any external libraries does not contains table component).

Of course we had some hard time with JavaFX as it is really young technology, but still we loved how easily we could create effectively looking and working components. Not mentioning BIND functionality which is one of the things that JavaFX developers will fall in love from the first sight :)

But a week ago our customer decided that he needs pluginable envierment and almous every element in the system needs to be a separate plugin. In the begining I was crushed. I thought that one month of our hard work will go to trash.

"We can put our JavaFX components into swing application, I've seen examples on the internet" - said my manager. So I've started looking for it and I have found not really nice looking hack called JFXScene (http://blogs.sun.com/javafx/entry/how_to_use_javafx_in), but also in comments to that article I've found info that this hack is not working with in JavaFX 1.2. But my manager said that for sure this is possible and he will do it even so google does not know it :) And he did :) How ? It is really simple, a lot simpler then a hack described in mentioned link. In few sections I will try to explain how to put JavaFX components into swing application and then into Netbeans Platform.

1. Let's start!

First we need some JavaFX component that we will try to put into swing application. Lets create our own button
public class NiceLookingButton extends CustomNode, JFXScene{
  var color = Color.LIGHTGRAY;
  var button:Rectangle;

  override function create():Node{
    var text = Text{
      content:"Button"
      fill: Color.WHITE
      font: Font{
        size: 20
      }
    }

    button = Rectangle{
      x: 0 y: 0
      width: 100 height: 50
      arcHeight:10 arcWidth: 10
      strokeWidth: 3
      stroke: bind color
      fill: LinearGradient{
        startX: 0 endX: 0
        startY: 0 endY: 1
        stops: [
          Stop{ color: Color.WHITE offset: 0},
          Stop{ color: color offset: 0.3}
        ]
      }
      onMouseEntered: function(event){
        color = Color.LIGHTBLUE;
        setFill();
      }
      onMouseExited: function(event){
        color = Color.LIGHTGRAY;
        setFill();
      }
}
    Stack{
      effect: Reflection{}
      content:[
        button,
        text
      ]
    } 
function setFill(){
    button.fill = LinearGradient{
      startX: 0 endX: 0
      startY: 0 endY: 1
      stops: [
        Stop{ color: Color.WHITE offset: 0},
        Stop{ color: color offset: 0.3}
      ]
    }
  }

}


2. Prepare to swing.


Now, when we have really pretty :) JavaFX component we can prepare it for swinging. To do this first we need to create MIXIN class.
What is mixin? Mixin was added in JavaFX 1.2 and it is a special type of class, it can not be constructed but other classes can implement multiple inheritance with them. In other words normal classes can extends many mixin classes.

Our mixin implements method that puts component into JavaFX Scene and in the end gets Object that extends JComponent.
public mixin class JFXScene {
  public function getScene():JComponent{
    var scene = Scene{content: this as Node};
    var swingScene = scene.impl_getPeer() as SwingScene;
    return swingScene.scenePanel;
  }
}

Now we can change implementation of NiceLookingButton:

public class NiceLookingButton extends CustomNode, JFXScene {
  //no changes here
}


The last thing we have to do is to add JavaFX 1.2 run time library ( which can be download from here : http://dl.javafx.com/javafx-rt-windows-i586__V1.2.0_b233.jar ) to our project class path.

Is it all? YES! it is THAT simple! we are now ready to put JavaFX component into normal Swing application!

3. Lets SWING!

Now we can create Simple Swing application, and put on its classpath javafx-rt downloaded in previous section and jar file with our JavaFX Component.

public class MainFrame extends javax.swing.JFrame {
  public MainFrame() {
    initComponents();
  }

  private void initComponents() {
    setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
    getContentPane().setLayout(new FlowLayout());
    getContentPane().add(new NiceLookingButton().getScene());
    pack();
  }

  public static void main(String args[]) {
    java.awt.EventQueue.invokeLater(new Runnable() {

      public void run() {
        new MainFrame().setVisible(true);
      }
    });
  }
}


As you can see in the code we do not use any hack or reflection to create our button, we just use normal no-argument constructor like we would create every other Java class. Isn't this nice?

Since in swing application we have references to NiceLookingButton we can invoke any public methods like on any other java object.

3. "New construction options"

If you have worked with JavaFX before you probably know that in JavaFX Scirpt you do not declare any constructors, you create object in very nice "groovy-like" way:
MyJavaFXObject{firstName: "Michal", surename: "Margiel"  }
As you can see it is really nice, but it determines that in classes compiled from JavaFX Script ces we have only default, no-argument constructor. But what if we would like to pass some information during object construction? Well... No problem ! we just need to introduce static factory method.

Since there is no static key word in JavaFX all static members are declared like any other but outside class.
So if we would like to pass i.e. width and height of the button we need to introduce those properties as public variables and introduce this method:

public function create(width:Integer, height:Integer):NiceLookingButton{
  NiceLookingButton{width: width, height: height}
}

public class NiceLookingButton extends CustomNode, JFXScene{
  (...)
}



Now we just need to rebuild our library and from now on we can invoke our factory method.

private void initComponents() {
  setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
  getContentPane().setLayout(new FlowLayout());
  NiceLookingButton button = NiceLookingButton.create(200, 100);
  getContentPane().add(button.getScene());
  pack();
}


4. Push the Button

Ok, So we can construct JavaFX objects with parameters and put them into Swing applications, also we know that we can invoke all public methods on them. But our component is a Button so it should be able react on clicking on it on swing side. Can we do it? Again - YES!

We need to introduce public variable with type function() inside NiceLookingButton
public class NiceLookingButton extends CustomNode, JFXScene{
  public var width:Integer = 100;
  public var height:Integer = 50;
  public var onMouseClick:function();
  (...)
}

and then invoke it when user click on our button (Rectangle)
onMouseClicked: function(event){
  onMouseClick();
}

after rebuilding library we have access to $onMouseClick property which accepts Function0 . Function0 is an interface with method

public Void invoke()

which will be invoke when user will click our button. Lets introduce simple implementation:

button.$onMouseClick = new Function0(){
  public Void invoke() {
    System.out.println("Nice Looking JavaFX Button Clicked!");
    return null;
  }
};

As you might noticed invoke method returns Void Object so we have to return at least null;



5. JavaFX Platform?

Ok, so the question for now is: Since NetBeans Platform uses Swing, can we also use JavaFX inside it?
and the answer is of course - YES :) and it is again really, really simple!
We just have to fallow few steps:
  1. Wrap up jar containing NiceLookingButton and javafx-rt into Netbeans Platform Modules.
  2. Add javafx-rt module to yourJavaFXLibrary module.
  3. Create Netbeans Platform Module with Window Component and add both yourJavaFXlibrary and javafx-rt module.
  4. Add NiceLookingButton to your TopComponent,
  5. private void initComponents() {
      setLayout(new FlowLayout());
      NiceLookingButton button = NiceLookingButton.create(200, 100);
      button.$onMouseClick = new Function0(){
        public Void invoke() {
          System.out.println("Nice Looking JavaFX Button Clicked!");
          return null;
        }
      };
      add(button.getScene());
    }
    
  6. Run NetBeans Platform project, and voilà!
We can see our button in Netbeans Platform, and all actions (like hovering and clicking) still works!

6. Yes, Yes, Yes!

So conclusion is really great!.
  • Yes! - you can use your JavaFX 1.2 components without any reflection inside Swing applications.
  • Yes! - you can invoke methods on javaFX components from swinf and oposite
  • and Yes! - you can use JavaFX in Netbeans Platform to build JavaFX Platform :)
(from left JavaFX app, Swing app with JavaFX component, and NetBeans Platform with JavaFX component)

All three NetBeans projects (JavaFX, Swing and NetBeans Platform) are available here.
In case of any questions just write to me!

13 komentarzy:

Unknown pisze...

Would you mind if I asked about providing more information on the application and what JavaFX components were used?

Michal Margiel pisze...

:P

Roger Wilco pisze...

Thanks, Michal. That was very useful. It's a little more tricky with Eclipse: you need to replace a jar in the SDK with the runtime jar.
I talk about it in my blog: http://softwarecrisis.blogspot.com/

Michal Margiel pisze...

Thx,

I am really happy that my post helped you. And thank you for your update about eclipse!

Shai Bentin pisze...

It was a very interesting read, however it does not seem to work for me. The following line does not compile:

getContentPane().add(new NiceLookingButton().getScene());

Shai Bentin pisze...

Forget it, just a netbeans problem!

For FX beginners, note that you should compile your fx components separately, get the jar and use it in your swing app.

Michal Margiel pisze...

Hi Shai,
I am really happy that you manage to solve your problem by your own.
It is nice to read that our work and my post help some other people!

Cheers!

Koziołek pisze...

Very useful path to create custom Swing components.

@Shai Bentin:
What do you thing about connect this with maven as separately modules?

Unknown pisze...

Hi, I was trying your demo. All works fine, but when I add some functionality in the JavaFX code a than click on build project and again import the jar into the swing application, it doesn't import the JavaFX libraries from the jar file. Can somebody help me how to make the jar file from JavaFX project and import it into Swing one?

Thx a lot.

Radek

Unknown pisze...

Hi, the zip link that contains the source seems broken, could you fix it?. Do you know if your example runs in 1.3? Im doing step by step and it doesn't work.
Andrés.

Unknown pisze...

Im having this error when I just run NiceLookingButton:

cannot access com.sun.scenario.scenegraph.JSGPanel

do you know why?

helloadam87 pisze...

Hi Andres,

you should add Scenario-0.6.jar (http://download.java.net/javadesktop/scenario/releases/0.6/) to your classpath to solve this problem
cannot access com.sun.scenario.scenegraph.JSGPanel

Thanks a lot Michał Margiel for this wonderful tutorial _^^_
Have a nice day

Michal Margiel pisze...

Thank you very much for your appreciation!
But to be honest I think this tutorial is more or less useless now (well more then a year now :) ) when Oracle decided to remove delete javaFX script and switch to good old java.