Introduction to JavaFX Script




by Anghel Leonard, OnJava.com


Seite 1, 2

JavaFX code can be easily integrated with Java code. Here is an example that uses JavaFX to load an image into a frame and allows the user to select a rectangular zone and save it. The capture and save operations are accomplished using Java code.

Listing 15
import java.io.*;
import javafx.ui.*;
import javafx.ui.canvas.*;
import javafx.ui.filter.*;
import java.awt.Robot;
import java.awt.Rectangle;
import java.awt.image.RenderedImage;
import javax.imageio.ImageIO;
import java.lang.System;
class CaptureExample extends CompositeNode{
attribute lx: Integer;
attribute ly: Integer;
operation CaptureExample();
}
attribute CaptureExample.lx = 0;
attribute CaptureExample.ly = 0;
operation saveCapture(lx_copy:Integer, ly_copy:Integer) {
var robot = new Robot();
var rect = new Rectangle (lx_copy, ly_copy, 50, 50);
var BI=robot.createScreenCapture(rect);
var file = new File(".//capture.jpg");
ImageIO.write((RenderedImage)BI, "jpg", file);
}
function CaptureExample.composeNode() =
Group{
    transform: []
    content:[ImageView {
    transform: []
    image: Image { url: ".//app//Sunset.gif" }
    cursor: DEFAULT
    onMouseClicked: operation(e:CanvasMouseEvent) {
    saveCapture(e.source.XOnScreen,e.source.YOnScreen);
    }
    onMouseMoved: operation(e:CanvasMouseEvent) {
    lx = e.x;
    ly = e.y;
    }
    },
    Rect{
    x: bind lx
    y: bind ly
    width: 50
    height:50
    strokeWidth: 1
    stroke: black
    }]
    };
Frame {
    centerOnScreen: true
    visible: true
    height: 230
    width: 300
    title: "Capture the screen..."
    onClose: operation() {System.exit(0);}
    content: ScrollPane {
    background: white
    view: Canvas {
    background: black
    cursor: DEFAULT
    content: CaptureExample
    }
   }
}

Notice the use of bind. This is a very important JavaFX operator used for incremental and lazy evaluation of attributes. You can find out more about this operator in the JavaFX Programming Language documentation.

Also, notice that, it is possible to interact in the above application using two mouse events: mouse clicked (onMouseClicked) and mouse moved (onMouseMoved). JavaFX supports the following mouse events:

  • onMouseClicked
  • onMouseMoved
  • onMousePressed
  • onMouseExited
  • onMouseEntered
  • onMouseReleased
  • onMouseDragged

For asynchronous execution of code you can use the JavaFX do later statement like this:

Listing 16
//asynchronous execution with do later statement
import java.lang.System;
var s1 = "My name is ";
var s2 = "Anghel Leonard";
    do later {
    System.out.println(s2);
    }
System.out.println(s1);

Result: My name is Anghel Leonard

JavaFX allows you to execute a portion of code in a separate thread by placing that code into a do statement. Using this technique the AWT Event Dispach Thread will be able to process all the incoming events. Here is an example that uses an infinite loop into a do statement. Notice that even if you have a infinite loop you still can close the window normally.

Listing 17
import javafx.ui.*;
import java.lang.System;
import javafx.ui.canvas.*;
import java.util.Random;
class DoExample extends CompositeNode{
attribute randomfill: Color;
operation changeOpacity();
}
attribute DoExample.randomfill = Color{red:0 green:0 blue:0};
operation DoExample.changeOpacity() {
do{
    while(true)
    {
    var r = new Random();
    var g = r.nextInt(255);
    randomfill = Color{red:g green:g blue:g};
    }
}
}
function DoExample.composeNode() =
Group {
    transform: []
    content: [
    Text {
    x: 20
    y: 20
    content: "Because of \"do\" you can close this window..."
    font: Font {face: VERDANA, style: [ITALIC, BOLD], size: 20}
    fill: bind randomfill
    opacity: 0.5
    onMouseClicked: operation(e:CanvasMouseEvent) {
    changeOpacity();
    }
    }]
};
Frame {
    centerOnScreen: true
    visible: true
    height: 100
    width: 580
    title: "\"Do\" example..."
    onClose: operation() {System.exit(0);}
    content: ScrollPane {
    background: white
    view: Canvas {
    background: black
    cursor: DEFAULT
    content: DoExample
    }
    }
}

JavaFX code can be integrated with HTML code using the JavaFX Label class. This class supports HTML and CSS in the same manner as a web application. Here is an example that renders an HTML table.

Listing 18
import javafx.ui.*;
import java.lang.System;
import javafx.ui.canvas.*;
class Partners {
    attribute name: String;
    attribute company: String;
    attribute phone: String;
    attribute e_mail: String;
    attribute partners: Partners*;
    }
    var myPartners = Partners {
    partners: [Partners{
    name: "Mary J"
    company: "Software ATV Inc."
    phone: "0900090345"
    e_mail: "maryj@yahoo.com"
    },
    Partners{
    name: "Leona W"
    company: "Winkle LTD"
    phone: "090849435"
    e_mail: "leonaw@yahoo.com"
    },
    Partners{
    name: "Joe T"
    company: "Press OJ"
    phone: "340909879"
    e_mail: "joet@yahoo.com"
    }]
    };
    Frame {
    content: Label {
    text: bind "<html>
    <h2 align='center'>- My Partners -</h2>

    <table align='center' border='0' bgcolor='#BBAAEE'>
    <tr bgcolor='#FFEE55'>
    <td><b>Name</b></td>
    <td><b>Company</b></td>

    <td><b>Phone</b></td>
    <td><b>E-mail</b></td>
    </tr>

    {
    if (sizeof myPartners.partners == 0)
    then "<tr bgcolor='#432211'><td colspan='8'><b>
    I have no partners...</b></td></tr>"
    else foreach (t in myPartners.partners)
    "<tr bgcolor='#FF25AD'>
    <td>{t.name}</td>

    <td>{t.company}</td>
    <td>{t.phone}</td>
    <td>{t.e_mail}</td>
    </tr>"
    }
    </table>

    </html>"
    }
    visible: true
    }

Running listing 18
Figure 7. Running Listing 18

In the last section of this article we will see two applications that illustrate how easy it is to build complex animations using JavaFX. The first application simulates an analog clock. The design is based on four images animated with JavaFX code.

Listing 19
import javafx.ui.*;
import javafx.ui.canvas.*;
import javafx.ui.filter.*;
import java.lang.System;
class Clock extends CompositeNode{
attribute seconds: Number+;
attribute minutes: Number+;
attribute hours: Number+;
operation startClock();
}
attribute Clock.seconds = 360;
attribute Clock.minutes = 360;
attribute Clock.hours = 360;
operation Clock.startClock() {
do{
    while(true)
    {
    if(seconds>=360) {seconds = [0,6..360]
     dur 60000 linear;}
    if(minutes>=360) {minutes = 0; minutes = [0,6..360]
     dur 3600000 linear;}
    if(hours>=360) {hours = 0;hours = [0,6..360]
     dur 43200000 linear;}
    }
    }
}
function Clock.composeNode() = 
Group {
    onMouseClicked: operation(e:CanvasMouseEvent) {
    startClock();
    }
    transform: []
    content:[ImageView {
    image: Image { url: ".//app//clockempty.gif" }
    },
ImageView {
    transform: bind [translate(203,82),
     rotate (seconds,2.5,125)]
    image: Image { url: ".//app//l1.gif" }
    antialias: true
    },
ImageView {
    transform:  bind [translate(203,100),
     rotate (minutes,2.5,107)]
    image: Image { url: ".//app//l2.gif" }
    antialias: true
    },
ImageView {
    transform:  bind [translate(203,115),
     rotate (hours,2.5,92)]
    image: Image { url: ".//app//l3.gif" }
    antialias: true
    },
Circle {
    cx: 205
    cy: 205
    radius: 13
    fill: red
    strokeWidth: 1
    }]
};
Frame {
    centerOnScreen: true
    visible: true
    height: 450
    width: 450
    title: "JavaFX - Clock"
    onClose: operation() {System.exit(0);}
    content: ScrollPane {
    background: white
    view: Canvas {
    background: black
    cursor: DEFAULT
    content: Clock
    }
    }
}

Running listing 19
Figure 8. Running Listing 19

Notice the dur (duration) operator. JavaFX provides this operator for creating animations. It is used to apply an array asynchronously to a time interval. Before returning an element from the array JavaFX will wait a time equal to the specified milliseconds. This process will be repeated until the whole elements are returned. There are four types of interpolations:

  • linear
  • easein
  • easeout
  • easeboth

By default it is a ease-in, ease-out combination.

The second application simulates a set of pop-up menus that can be dragged on the screen. With a simple click on the menu headers you can hide/show the items using a nice opacity effect. The whole design was written with JavaFX drawing capabilities:

Listing 20
import javafx.ui.*;
import javafx.ui.canvas.*;
import javafx.ui.filter.*;
import java.lang.System;
class MenuOptions extends CompositeNode{
attribute px: Integer;
attribute py: Integer;
attribute lx: Integer;
attribute ly:Integer;
attribute lw:Integer;
attribute itemsOpacity:Number;
attribute menutext: String;
}
trigger on new MenuOptions {
    this.px = 0;
    this.py = 0;
    this.menutext = "";
    this.lx = 0;
    this.ly = 0;
    this.lw = 150;
    this.itemsOpacity = 0.0;
    }
function MenuOptions.composeNode() = 
Group {
    transform: bind []
    opacity: bind itemsOpacity
    content:[Rect {
    x: bind lx
    y: bind ly
    width: lw
    height: 20
    arcHeight: 10
    arcWidth: 10
    fill: Color {red:190 green:181 blue:215}
    stroke: Color {red:68 green:54 blue:103}
    strokeWidth: 2
    onMouseEntered: operation(e:CanvasMouseEvent) {
    if(itemsOpacity == 0.7) {itemsOpacity = 1.0;}
    }
    onMouseExited: operation(e:CanvasMouseEvent) {
    if(itemsOpacity == 1.0) {itemsOpacity = 0.7;}
    }
    onMouseClicked: operation(e:CanvasMouseEvent) {
    eventListener(this.menutext);
    }
    },
    Text {
    x: bind lx+5
    y: bind ly+5
    content: bind menutext
    font: Font {face: VERDANA, style: [BOLD], size: 11}
    fill:Color {red:68 green:54 blue:103}
    }]
    };
class MainMenu extends CompositeNode{
attribute option: String;
attribute px: Integer;
attribute py: Integer;
attribute lx: Integer;
attribute ly:Integer;
attribute lw:Integer;
attribute menutext: String;
attribute step: Integer;
attribute submenu: MenuOptions+;
operation addSubmenu(t:MenuOptions);
operation show_hide();
}
trigger on new MainMenu {
    this.option = "";
    this.px = 0;
    this.py = 0;
    this.menutext = "";
    this.lx = 0;
    this.ly = 0;
    this.lw = 150;
    this.step = 20;
    this.submenu = null;
    }
operation MainMenu.addSubmenu(t:MenuOptions) {
t.lx = this.lx;
t.lw = this.lw;
t.ly = this.ly+step;
step=step+20;
insert t into submenu;
}
operation MainMenu.show_hide() {
if(submenu.itemsOpacity[1] == 0.7){submenu.itemsOpacity =
 [0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0] dur 1200;}
    else if(submenu.itemsOpacity[1] == 0.0){
    submenu.itemsOpacity =
     [0.1,0.2,0.3,0.4,0.5,0.6,0.7] dur 1200;}
}
function MainMenu.composeNode() = 
Group {
    transform: bind []
    content:[Rect {
    x: bind lx
    y: bind ly
    height: 20
    width: bind lw
    arcHeight: 10
    arcWidth: 10
    fill: Color {red:68 green:54 blue:103}
    stroke: Color {red:190 green:181 blue:215}
    strokeWidth: 2
    onMouseDragged: operation(e:CanvasMouseEvent) {
    lx += e.localDragTranslation.x;
    ly += e.localDragTranslation.y;
    submenu.lx += e.localDragTranslation.x;
    submenu.ly += e.localDragTranslation.y;
    }
    onMouseClicked: operation(e:CanvasMouseEvent) {
    show_hide();
    }
    },
    Text {
    x: bind lx+5
    y: bind ly+5
    content: bind menutext
    font: Font {face: VERDANA, style: [BOLD], size: 11}
    fill:Color {red:190 green:181 blue:215}
    },
    bind submenu]
};
var menu_1 = new MainMenu();
menu_1.lx = 120;
menu_1.ly = 140;
menu_1.lw = 128;
menu_1.menutext = "Navigate";
var submenu_11 = new MenuOptions();
submenu_11.menutext = "Go to Class...";
var submenu_12 = new MenuOptions();
submenu_12.menutext = "Go to Test";
var submenu_13 = new MenuOptions();
submenu_13.menutext = "Back";
var submenu_14 = new MenuOptions();
submenu_14.menutext = "Forward";
var submenu_15 = new MenuOptions();
submenu_15.menutext = "Go to Line...";
menu_1.addSubmenu(submenu_11);
menu_1.addSubmenu(submenu_12);
menu_1.addSubmenu(submenu_13);
menu_1.addSubmenu(submenu_14);
menu_1.addSubmenu(submenu_15);
var menu_2 = new MainMenu();
menu_2.lx = 260;
menu_2.ly = 140;
menu_2.lw = 90;
menu_2.menutext = "Refactor";
var submenu_21 = new MenuOptions();
submenu_21.menutext = "Rename....";
var submenu_22 = new MenuOptions();
submenu_22.menutext = "Pull Up...";
var submenu_23 = new MenuOptions();
submenu_23.menutext = "Push Down...";
menu_2.addSubmenu(submenu_21);
menu_2.addSubmenu(submenu_22);
menu_2.addSubmenu(submenu_23);
operation eventListener(s:String) {
System.out.println("You choose:{s}");
}
Frame {
    centerOnScreen: true
    visible: true
    height: 500
    width: 500
    title: "JavaFX - Menu"
    onClose: operation() {System.exit(0);}
    content: ScrollPane {
    background: white
    view: Canvas {
    background: black
    cursor: DEFAULT
    content: [menu_1, menu_2]
    }
    }
}

Running listing 20
Figure 9. Running Listing 20

Notice that JavaFX uses triggers (like SQL) instead of constructors. A trigger is marked by the trigger keyword and is made of a header and a body. The header indicates the event that must occur before executing the body content. A trigger can be created, inserted, deleted, or replaced. You can find more details about triggers on the OpenJFX site.

Conclusion

JavaFX Script is a capable new language for the Java platform. With it, you can easily build rich, dynamic interfaces in much less time than you could build something comparable in Java with Swing and Java 2D. In this article, we have stepped through the basic syntax, looked at IDE support, and built a demonstration application that shows off some of JavaFX's Capabilities. JavaFX will quickly become a necessary tool in the Java developer's toolbox.

Resources

Anghel Leonard is a senior Java developer with more than 12 years of experience, specializing in GIS applications. He has written two books about XML and Java.



<< Zurück zu Seite 1 <<


Themen

Buchreihen

Special Interest

International Sites

O'Reilly China O'Reilly France O'Reilly USA O'Reilly Japan O'Reilly Taiwan