Friday May 29, 2009

JavaFX Borders


When I am not busy with my day job or writing books on JavaFX, JavaFX - Developing Rich Internet Applications, I spend my spare time helping out on the open source JFXtras project, http://code.google.com/p/jfxtras/ . One part I have contributed is the borders package. The idea started off trying to duplicate some of the borders found in Swing. However, finding it relatively easy to implement the Border classes in JavaFX, the types of Borders started to grow.

The following screen snapshot shows the current set of  borders:

Border Ensemble

To create a border just import the Border package from the JFXtras project and then include it in your scenegraph. For example, to implement the PipeBorder from the ensemble above, the code looks like:

import org.jfxtras.scene.border.PipeBorder;
import javafx.scene.image.ImageView; 
import javafx.scene.image.Image;
def image = Image {
    url: "{__DIR__}images/Mona_Lisa.jpg"
    width: 100
    height: 150
    preserveRatio: true
};
var pipeBorder  = PipeBorder {
    content: ImageView { image: image }
}; 

All the borders are created as JavaFX Controls and have corresponding Skins that allow you to modify their appearance either programatically or via CSS Style sheets. For the PipeBorder example, it is possible to have the pipe raised or lowered. A lowered pipe has its raised variable set to false and reverses the coloring of the border. This ends up looking like:

Pipe Border Lowered

You can also control the coloring of the border through CSS Style like statements. In the above Ensemble, the FrameBorder used such a statement to get the coral colored border:

VBox {
   spacing: 5
   nodeHPos: HPos.CENTER
   content: [
      FrameBorder {
          style: "background-fill: coral;"
          content: ImageView { image: image }
      },
      Label { text: "FrameBorder" }
   ]
}

If we change this to another color, say aquamarine, we get the following:

Frame Border

One of the more interesting borders is the ShapeBorder.

Shape Border

This uses any JavaFX Shape object as a clip over the underlying content. In this case, the Mona Lisa image. This example uses the JFXtras Star2 Shape class.

import org.jfxtras.scene.border.ShapeBorder;
import org.jfxtras.scene.shape.Star2;

VBox {
    spacing: 5
    nodeHPos: HPos.CENTER
    content: [
        ShapeBorder {
            var anode: Node;
            shape: Star2 {
                centerX: bind anode.layoutBounds.width/2
                centerY: bind anode.layoutBounds.height/2
                outerRadius: bind anode.layoutBounds.width/2
                innerRadius: bind anode.layoutBounds.width/6
                count: 5
            }
            content: anode = ImageView { image: image }
            },
        Label { text: "ShapeBorder" }
   ]
}

As you can guess, there are way too many options to explain in a short blog, but I hope I have peaked your interest to check out the JFXtras project.

You can get the source code for the above examples from here and, of course, the Mona Lisa from here.

Next week is JavaONE and my schedule is already full to the max. See y'all there.


Thursday Mar 12, 2009

JavaFX - Gator JUG April 8, Gainesville FL, Orlando JUG - April 23, Orlando

I will be doing a live JavaFX development presentation for the Gator and Orlando Java User Groups in April. Details can be found at Gator JUG - Gainesville and Orlando JUG - Orlando .

Instead of the plain old slide presentation, I plan on showing live development of a JavaFX application using Netbeans IDE for JavaFX (thanks to Josh Marinacci for the inspiration). During the session, I will touch on some of the key features of JavaFX and demonstrate a simple JSON REST application. Lately, I have been contributing to the JFXtras project, and will also demonstrate some of those classes.

If you are in Central Florida, please plan on attending one of these sessions.

If you can't attend, I will be posting the sample application on our book's website JavaFX-Developing Rich Internet Applications sometime in April.

jim

Monday Feb 16, 2009

Finishing first cut - JavaFX - Developing Rich Internet Applications

Jim Connors, Eric Bruno and I have feverishly been heads down writing a new Book on JavaFX, JavaFX - Developing Rich Internet Applications.  We have just finished the initial writing and soon the rough cut chapters will be up on Safari for review. Target date for publication is June, 2009, in time for JavaONE.

We are also in the process of building a web site, jfxbook.com, to post the code samples, and show off some of the applications featured in the book. Most of the code samples are already posted up there, so feel free to peruse them. This is a work in progress, so check back for updates.

There are two applets that you can run. The NasaImageBrowser shows the US National Aeronautics and Space Administration (NASA) image of the day catalog. Also, Jim Connors has posted his Sudoku game application, so give it a try.

Once, the rough cuts are up on Safari, I'll let you know so you can start reading. We always appreciate any feedback.


Saturday Jan 03, 2009

JavaFX Progress Bar with glow effect

Yesterday, I was looking at an install package for some game software my son was loading on his computer and I was intrigued by the progress bar. It had a glow effect going back and forth while the progress bar was "progressing".  Of course, I had to sit down and figure this out in JavaFX.

To start with I borrowed some code from my IndeterminateProgressBar that I posted here a few moon's ago,  JavaFX - Inverting text color with a changing background. This shows how to change the text color as the progress bar crosses over the text.

Next, I created a ProgressBar class, that shows a progress bar as it moves from zero to a maximum. There are 2 options for the display, the percentage completion or the current value. To show progress, I merely paint a Rectangle for the progress with a different color over a background Rectangle.  The width of this "progress bar" changes based upon the percentage completion. So far, nothing surprising here.

To create the moving glow effect, I created an Ellipse with a GuasianBlur effect:


Ellipse {
centerX: bind blurX
    centerY:  bind height/2
    radiusX: bind progressHeight
    radiusY: bind progressHeight/2
    fill: Color.rgb(255,255,255,.7)
effect: GaussianBlur{ radius: 30 }
},


The centerX of the Ellipse will be changed in an animation based on the local variable, blurX, that moves the "glow" back and forth over the progressBar part. Here is the animation:


    var blurAnim = Timeline {
        repeatCount: Timeline.INDEFINITE
        autoReverse: true
        keyFrames: [
            at(0s) { blurX => 0.0 }
            at(2s) { blurX => currentX - progressHeight/2 tween Interpolator.EASEBOTH }
        ]
    };


Next, I tie the animation play to when the Progress bar is visible:


var playing = bind visible on replace {

        if(playing) {
            blurAnim.play();
        }else {
            blurAnim.stop();
        }

    };


The effect looks like this. Notice the glow towards the left side.

Progress Bar %75

And at %83, the glow is after the %83.58 label.

Progress Bar @ %83

The glow actually moves back and forth over a 2 second cycle.

One thing I haven't taken the time to figure out, is how to have the glow move at a constant rate of speed. In the timeline, I set the duration to a fixed 2 seconds, but as the progressbar's width grows, the glow travels a longer distance, so it moves faster, each cycle is still 2 seconds. (Thanks to Mr Newton and Mr. Einstien for this phenomenon).

Next, when I get a chance to play, I want to modify this to use CSS Style Sheets  and change it to a javafx.scene.Control with a javafx.scene.Skin. StyleSheets is a cool feature in JavaFX, that lets you easily modify the look of the JavaFX components, similar to the way HTML uses them. More on this later.

ProgressBar.fx is here.

Main.fx is here.

Thursday Dec 18, 2008

JavaFX 1.0 - Reflections on the JSON Weather Service

In my previous posting on the JSON Weather Service, I hard coded the Pull Parser's population of the Weather object. I don't particularly like to hard-code anything, so I have been investigating how to make this a little more generic. I found an answer using the JavaFX reflection package.

 What I did was create a class called "ParseModel". ParseModel has public instance variables for the "model" object (the Weather Object that will be populated), an optional "map" instance variable that contains maps between the parsed element name and the name of the instance varaible within the model object, a "url" that will point to the GeoNames geographical database, a "documentType" that is either PullParser.XML or PullParser.JSON, and an "onDone" function that is called when all the parsing is complete. I also have 3 private variables that hold the javafx.reflect types for the model object. context holds the javafx.reflect.FXLocal.Context instance, classRef holds the class type of the model object, and fxModel to hold the javafx.reflect.FXObjectValue that will be a mirror of the model object.


public class ParseModel {

    def context:FXLocal.Context = FXLocal.getContext();
    var classRef: FXClassType;
    var fxModel: FXObjectValue;

    public-init var model:Object on replace {
        if(model != null) {
            classRef = context.findClass(model.getClass().getName());
            fxModel = context.mirrorOf(model);
        }else { classRef = null; fxModel = null;}
    };

    public-init var map: Map;
    public-init var url:String;
    public-init var documentType:String;
    public-init var onDone: function(model:Object) : Void;


When the model is set, the java.reflect.FXLocal.Context instance is used to locate the javafx.reflect.ClassRefType  for the model class, and to create a javafx.reflect.FXObjectValue  object that encapsualtes the model Object. The classRef will be used to query the model for instance variables, and the fxModel variable will be used when setting the values for those variables.

Next, I create a function to be used by the PullParser to handle the parse events. This function translates the type from the parsed element to the type within the model object. Notice that when I set the value of the instance variable member object, I have to encapsulate the event value using context.mirrorOf().


function handler(event: Event) : Void {
        if(event.type == PullParser.TEXT or
            event.type == PullParser.INTEGER or
            event.type == PullParser.NUMBER ) {
            var field:String; // holds the name of the instance variable within the model object.
            if(map != null and map.containsKey(event.name)) { // a map entry exists, use it
                field = map.get(event.name) as String;
            } else { // default to the event name
                field = event.name;
            }
            var member = classRef.getVariable(field); // get the instance variable within the model object, for the field.

            var type = "{member.getType()}"; // the type of the instance variable within the model object.
            // try to guess the conversion
            if(event.type == PullParser.TEXT){
                if(type == "Integer") {
member.setValue(fxModel, context.mirrorOf(java.lang.Integer.valueOf(event.text)));
                } else if (type == "Number") {
member.setValue(fxModel, context.mirrorOf(java.lang.Double.valueOf(event.text)));
                } else if (type == "Boolean") {
member.setValue(fxModel, context.mirrorOf(java.lang.Boolean.valueOf(event.text)));
                }else { // assuming string
member.setValue(fxModel, context.mirrorOf(event.text));
                }
            } else if(event.type == PullParser.INTEGER) {
                if(type == "Number") {
member.setValue(fxModel, context.mirrorOf(java.lang.Double.valueOf(event.integerValue)));
                } else {
member.setValue(fxModel, context.mirrorOf(java.lang.Integer.valueOf(event.integerValue)));
                }
            } else if(event.type == PullParser.NUMBER) {
                if(type == "Integer") {
member.setValue(fxModel, context.mirrorOf(java.lang.Integer.valueOf(event.numberValue.intValue())));
                } else {
member.setValue(fxModel, context.mirrorOf(java.lang.Double.valueOf(event.numberValue)));
                }
            } else if(event.type == PullParser.TRUE or event.type == PullParser.FALSE) {
member.setValue(fxModel, context.mirrorOf(event.booleanValue));
            }
        }
    }


Next, I created an init block to create a RemoteTextDocument with a PullParser. The PullParser's onEvent function variable is set to the handler described above. When RemoteTextDocument returns and the PullParser completes, I call the onDone function variable passing the model object. Doing it this way makes ParseModel a once use object. It could have just as easily been done in a function rather than in the init block, and you could change the public instance variables from public-init to public so they can change between invocations.


    init {
       var input:InputStream;
       println("Starting");
       var rtd:RemoteTextDocument = RemoteTextDocument {
            url: bind url
            onDone: function(success:Boolean):Void {
                if(success) {
                    var doc = rtd.document;
                    println(doc);
                    input = new ByteArrayInputStream(doc.getBytes());
var parser: PullParser = PullParser {
                        documentType: documentType
                        input: input
                        onEvent: handler
                    };

                    parser.parse();
                    input.close();
onDone(model);

                }else {
                    println("failure = {rtd.failureText}");
                }
                println("Done");
            }
        };
    }


To initialize this:


    var w = Weather {};
    var m: Map = HashMap{};
m.put("stationName", "station");// It is only necessary to put entries when names do not match

    var modl = ParseModel {
        model: w
        map: m
        documentType: PullParser.JSON
        url: "http://ws.geonames.org/weatherIcaoJSON?ICAO=KMCO"
        onDone: function(obj: Object ) : Void {
var weather = obj as Weather;
            println("MODEL: {weather}");

        }
    };


TODO: This program is still one dimensional, only handling the basic variable types of Boolean, Integer, Number, and String. An improvement would be to recognize complex object trees for the model and do the appropriate recursion during parsing.

A program to test these ideas out can be downloaded from here.


Saturday Dec 13, 2008

JavaFX 1.0 - JSON Weather Service Redux.

Last June, I demonstrated a JavaFX class to access the JSON Weather Service from the GeoNames geographical database at http://www.geonames.org/export/JSON-webservices.html#weatherJSON.  Please see my earlier blog, http://blogs.sun.com/clarkeman/entry/javafx_script_and_json_weather  for more information about the Weather Service and how I implemented it in a pre-release version of JavaFX.

With the release of JavaFX 1.0, much has changed so I decided to update this example. The main differences are that  XML and JSON parsing were totally refactored and the original json.async.AsyncJsonCall  class is no longer with us. However, you can still leverage the javafx.async.RemoteTextDocument  and the new javafx.data.pull.PullParser classes to obtain the same effect.

I enhanced the example a little, so now you can enter an airport code. While the data is being retrieved, I use the IndeterminateProgressBar class that is described in my recent post, JavaFX - Inverting Text Color.

The Weather class is basically the same as before. The new javafx.data.pull.PullParser class, is setup as a JSON parser and the onEvent action gets the data from the JSON stream and assigns it into the Weather class. PullParser can also be used for XML streams. 


package jsonweather;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.text.Text;
import javafx.scene.text.Font;
import javafx.scene.text.TextOrigin;
import javafx.scene.control.TextBox;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.data.pull.PullParser;
import javafx.data.pull.Event;
import javafx.async.RemoteTextDocument;
import java.net.URL;
import java.lang.System;
import java.io.InputStream;
import java.io.ByteArrayInputStream;

// ICAO code in US is typically 'K' + IATA airport code
// MCO is Orlando FL US IATA code, so ICAO is KMCO.
// New York - JFK is KJFK.
// See http://en.wikipedia.org/wiki/List_of_airports_by_ICAO_code
var airportCode = "MCO";
var url = bind "http://ws.geonames.org/weatherIcaoJSON?ICAO=K{airportCode.toUpperCase()}";

class Weather {
    public var station:String;
    public var clouds:String;
    public var windDirection:Integer;
    public var windSpeed:Number;
    public var temperature:Number;
    public var dewPoint:Number;
    public var humidity:Integer;
    public var seaLevelPressure:Number;
    public var observation:String;
}
var weather:Weather = Weather{};

var jsonInput: InputStream; // holds the input stream for the JSON data

// The JSON parser
var parser = PullParser {
    documentType: PullParser.JSON;
    input: bind jsonInput
    onEvent: function(event: Event) { // parse the JSON Weather data and populate the Weather object

        if(event.type == PullParser.END_VALUE) {
            //println("{event}");
            if(event.name == "clouds") {
                weather.clouds = event.text;
            }else if (event.name == "stationName") {
                weather.station = event.text;
            }else if (event.name == "windDirection") {
                weather.windDirection = event.integerValue;
            }else if (event.name == "windSpeed") {
                weather.windSpeed = java.lang.Double.valueOf(event.text);
            }else if (event.name == "temperature") {
                weather.temperature = java.lang.Double.valueOf(event.text);
            }else if (event.name == "dewPoint") {
                weather.dewPoint = java.lang.Double.valueOf(event.text);
            }else if (event.name == "humidity") {
                weather.humidity = event.integerValue;
            }else if (event.name == "seaLevelPressure") {
                weather.seaLevelPressure = event.numberValue;
            } else if (event.name == "observation") {
                weather.observation = event.text;
            }
        }
    }
}


var app:VBox;
var bar:IndeterminateProgressBar;
var input:TextBox;
var rtd:RemoteTextDocument;


var stage:Stage = Stage {
    title: "JSON Weather"
    width: 700
    height: 400
    scene: Scene {
        content: [ app=VBox {
            translateX: 50
            translateY: 50
            spacing: 10
            content: [
                HBox {
                    content: [
                        Text {
                            translateY: bind input.boundsInLocal.height/2;
                            content: "Airport Code:"
                        },
                        input = TextBox {
                            columns: 4
                            value: bind airportCode with inverse
                            selectOnFocus: true
                            action: function(): Void {
                                app.opacity = .5;
                                bar.visible = true;
                                println("airport code = {airportCode} url = {url}");
                                weather = Weather{};
                                if(rtd != null) rtd.cancel();

                                // The remote access from the Geo Names site.

                                rtd = RemoteTextDocument {
                                    url: bind url
                                    onDone: function(success:Boolean):Void {
                                        if(success) {
                                            var json = rtd.document;
                                            jsonInput = new ByteArrayInputStream(json.getBytes());
                                            parser.parse();
                                            jsonInput.close();
                                        }else {
                                            System.out.println("failure = {rtd.failureText}");
                                        }
                                        // reset back to original state
                                        app.opacity = 1.0;
                                        bar.visible = false;
                                        rtd = null;
                                    }
                                };

                            }
                        }
                    ]
                },
                HBox {
                    content: [
                        Text {
                            textOrigin: TextOrigin.TOP
                            content: "Station:"},
                        Text {
                            textOrigin: TextOrigin.TOP
                            content: bind weather.station
                            fill: Color.BLUE
                        }
                    ]
                },
                HBox {
                    content: [
                        Text {textOrigin: TextOrigin.TOP content: "Clouds:"},
                        Text {
                            textOrigin: TextOrigin.TOP
                            content: bind weather.clouds
                            fill: Color.BLUE
                        }
                    ]
                },
                HBox {
                    content: [
                        Text {textOrigin: TextOrigin.TOP content: "Wind Direction:"},
                        Text {
                            textOrigin: TextOrigin.TOP
                            content: bind "{weather.windDirection} degrees"
                            fill: Color.BLUE}
                    ]
                },
                HBox {
                    content: [
                        Text {textOrigin: TextOrigin.TOP content: "Wind Speed:"},
                        Text {
                            textOrigin: TextOrigin.TOP
                            content: bind "{weather.windSpeed} knots"
                            fill: Color.BLUE}
                    ]
                },
                HBox {
                    content: [
                        Text {textOrigin: TextOrigin.TOP content: "Temperature:"},
                        Text {
                            textOrigin: TextOrigin.TOP
                            content: bind "{weather.temperature}C degrees"
                            fill: Color.BLUE}
                    ]
                },
                HBox {
                    content: [
                        Text {textOrigin: TextOrigin.TOP content: "Dew Point:"},
                        Text {
                            textOrigin: TextOrigin.TOP
                            content: bind "{weather.dewPoint}C degrees"
                            fill: Color.BLUE}
                    ]
                },
                HBox {
                    content: [
                        Text {textOrigin: TextOrigin.TOP content: "Humidity:"},
                        Text {
                            textOrigin: TextOrigin.TOP
                            content: bind "{weather.humidity}%"
                            fill: Color.BLUE}
                    ]
                },
                HBox {
                    content: [
                        Text {textOrigin: TextOrigin.TOP content: "Sea Level Pressure:"},
                        Text {
                            textOrigin: TextOrigin.TOP
                            content: bind "{weather.seaLevelPressure}mb"
                            fill: Color.BLUE}
                    ]
                },
                HBox {
                    content: [
                        Text {textOrigin: TextOrigin.TOP content: "METAR Observation:"},
                        Text {
                            textOrigin: TextOrigin.TOP
                            content: bind "{weather.observation}"
                            fill: Color.RED}
                    ]
                },

            ]
        },
        bar = IndeterminateProgressBar {
            visible: false
            width: bind stage.width - 50
            height: 35
            text: "Loading..."
        }
        ]
    }
}

input.requestFocus();


I am using the new class, javafx.scene.control.TextBox, to enter an arbiritary airport code. After the code is entered, and the user hits the enter key, the action for the TextBox creates a RemoteTextDocument object with the latest airport code. This action also makes the IndeterminateProgressBar visible which in turn starts its animation. Once the RemoteTextDocument is done fetching the JSON stream, it invokes the PullParser to set the Weather attributes. When all is done, the progress bar is set to invisible, stoping its animation, and the new weather information is displayed. 

The following image is what the screen looks like while RemoteTextDocument is doing its work in the background. This shows a request to get the weather for Los Angeles (LAX).

LAX Weather Loading

And this is Orlando, after the weather data has been retrieved and parsed:

Orlando Weather

Thursday Dec 04, 2008

JavaFX - Inverting text color with a changing background.

Today, JavaFX 1.0 was announced (http://www.javafx.com), so I thought I would start chiming in again on some of the cool features of JavaFX.

I have been working with a customer to convert their Java Swing app over to JavaFX, and was intrigued with an indeterminate JProgressBar. An indeterminate JProgressBar has an internal rectangle, called a bouncing box, that continuously goes back and forth while running. What I noticed was that when the bouncing box moved over the progressString, it caused the text to change color. This was not a complete change, but only changed that part of the text that was covered by the bouncing box.

In the Swing source for JProgressBar (actually the Basic PLAF UI), this is done in the draw method, with the original text drawn once in the normal color (the selection background), followed by the alternate color, (the selection foreground), using a clip region. To duplicate this in JavaFX turns out to be similar.

I first created the bouncing box as a filled Rectangle and assigned it to an instance variable named "cursor". Then, I created a Text node with the background color, followed by a Text node with the foreground color. This second node is exactly the same as the first, except its stroke and fill colors are different. Furthermore, it takes a clip argument using the "cursor" rectangle. 


                cursor = Rectangle {
                    translateX: bind cursorX
                    translateY: bind borderWidth + 2
                    width: bind cursorWidth
                    height: bind height - borderWidth\*2 - 4
                    arcWidth: 5
                    arcHeight: bind height - borderWidth\*2 - 4
                    fill: bind background
                    cache: true
                },
                Text {
                    x: bind width/2 - geomText.boundsInLocal.width/2;
                    y: bind height/2 + geomText.boundsInLocal.height/2;
                    textOrigin: TextOrigin.BOTTOM
                    font: bind font
                    stroke: bind background
                    fill: bind background
                    content: bind text
                },
                Text {
                    x: bind width/2 - geomText.boundsInLocal.width/2;
                    y: bind height/2 + geomText.boundsInLocal.height/2;
                    textOrigin: TextOrigin.BOTTOM
                    font: bind font
                    stroke: bind foreground
                    fill: bind foreground
                    content: bind text

                    clip: bind cursor
                },


Now, the second Text does not display until the cursor Rectangle is over it, and only that part under the cursor is actually displayed.  Note that it is important that the clipped Text comes after the normal Text, because the last nodes are painted on top of previous nodes.

FX version of Indeterminate Progress Bar

The above screen snapshot shows the effect with the "ng..." of "Loading..." painted in white.

The complete code is available here!

Monday Mar 31, 2008

JavaFX Scripting api.

I read with interest Michael Heinrichs' series of posts showing how to interact with JavaFX Script objects from Java code using the Scripting API. To dig a little deeper, it is also possible to retrieve error messages from the compiled JavaFX Scripts.

The Scripting API, formally known as Scripting for the Java™ Platform, provides the basic framework for implementing the compilation and invocations as described in Micheal's blog entries. But what happens if the script has a compilation error or warning. How does your Java code get these messages. The answer is the javax.tools package with support for Diagnostics.

Both the javax.tools and javax.script packages are included in Java 6. For compatibility with Java 5, these packages are built into the JavaFX libraries. To use the Diagnostic feature, you must first cast the javax.script.ScriptEngine down to a JavaFXScriptEngine as Michael has done in his examples. The JavaFXScriptEngine contains versions of compile and eval methods that take a DiagnosticCollector argument. This object allows the JavaFX compiler to store any warnings, or errors encountered during the compilation phase.

To illustrate this, here is Michael's Java example from his blog entry, Creating JavaFX objects in Java programs, Code Sample 2, with modifications for capturing the Diagnostics. I have purposely changed the attribute in the MyJavaFXClass Object Literal declaration from the valid "property" attribute to an invalid "text" attribute shown in red in the code sample.

 1 import javax.script.ScriptEngineManager;
2 import javax.script.ScriptEngineManager;
3 import com.sun.javafx.api.JavaFXScriptEngine;
4 import javax.tools.Diagnostic;
5
import javax.tools.DiagnosticCollector;
6 import java.util.Iterator;
7 import java.util.List;
8 import javax.script.ScriptException;
9
10 public class Main {
11
12 public static void main(String[] args) {
13 ScriptEngineManager manager = new ScriptEngineManager();
14 JavaFXScriptEngine fxEngine =
15 (JavaFXScriptEngine) manager.getEngineByName("javafx");
16
17 DiagnosticCollector diags = new DiagnosticCollector();
18 try {
19 Object o = fxEngine.eval("MyJavaFXClass { text: \\"JavaFX class created in Java\\" }",
20 diags);
21 fxEngine.invokeMethod(o, "printProperty");
22
23 } catch (ScriptException e) {
24 List<Diagnostic> errorList = diags.getDiagnostics();
25 Iterator<Diagnostic> iter = errorList.iterator();
26 while (iter.hasNext()) {
27 Diagnostic d = iter.next();
28 System.out.println(
29 d.getKind().toString() + ": Line:" +
30 d.getLineNumber() + " Col:" + d.getColumnNumber() + "\\n'" +
31 d.getMessage(null) + "'");
32
}
33 } catch (Exception ex) {
34 ex.printStackTrace();
35 }
36 }
37 }

Code Sample 2: Constructing MyJavaFXClass in Java program, added Diagnostics

When this example is run, the following out appears:

ERROR: Line:1 Col:140
'cannot find symbol
symbol  : variable text
location: class MyJavaFXClass'


 

About

jimclarke

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today