“In art there are only fast or slow developments. Essentially it is a matter of evolution, not revolution.”
—Bela Bartok
A real advantage of JavaFX is that the entire platform is extended by the full power and capabilities of the Java platform. This allows existing Java frameworks and custom libraries to fully participate in a JavaFX application.
At its core, JavaFX classes are compiled into Java bytecode, so JavaFX, at the runtime level, is tightly integrated with the Java platform. However, the JavaFX language is a declarative language with an object-oriented flavor, whereas Java is an object-oriented imperative language. The main difference is that in JavaFX you tell the system what needs to be done and the runtime determines how to do it; in Java, you explicitly program what is to be done. When crossing the JavaFX/Java boundary, you must keep this difference in mind.
Basically, the JavaFX runtime is always in the background, so you need to be careful how you interact with JavaFX from Java. Nonetheless, it is still possible to leverage Java classes from JavaFX, and with a little effort, you can leverage JavaFX scripts within your Java code. This chapter describes some of the basic rules that must be obeyed when crossing the JavaFX/Java boundary.
The first part of this chapter discusses the rules for using Java objects and classes within JavaFX and you should be familiar with this to write JavaFX scripts. However, the last two sections, Java Scripting and JavaFX Reflection, discuss accessing JavaFX from Java. This is more important to people doing intense development with Java and JavaFX and by its nature requires a more in-depth knowledge of Java. If you are not interested in accessing JavaFX from Java, you may skip these last two sections.
As we mentioned in Chapter 3, JavaFX Primer, JavaFX classes can extend multiple Java interfaces, but can only extend at most one Java class, and that Java class must have a default (no args) constructor.
public class MyJavaFXClass extends java.awt.Point {
When extending a Java class, all the accessible methods and attributes of that class are available to the JavaFX class. However, none of the Java class attributes or functions may participate in JavaFX binding. Remember from Chapter 3 that JavaFX Script is a declarative language with object orientation and is a forgiving environment. Even if you try to bind to a Java object’s attribute, the runtime system will silently ignore the updates. To illustrate this
When this is run, f.x
is updated, but f.fxPointX
is not updated, and there is no error or warning message.
The main message here is you can extend Java classes, but the attributes and methods from those classes have no inherent connection to some of the advanced binding and trigger features found in JavaFX. For more detailed information on binding and triggers, check out Chapter 4, Synchronize Data Models—Binding and Triggers.
When extending Java interfaces or Java abstract classes, the JavaFX class must implement all the abstract methods contained within the interface or abstract class or be declared abstract itself. These function signatures must be able to map to the Java methods being implemented. For example, when extending the java.awt.event.ActionListener
interface, the Java method
public void actionPerformed(ActionEvent);
needs to be converted to a JavaFX function:
The override
keyword is required, as this instructs the system that this method overrides a method defined in the super class. The full example is
You can instantiate Java objects from JavaFX using one of two methods. First, if the Java object has a default (no args) constructor, you can use the JavaFX object literal syntax. However, you cannot initialize any of the Java object’s attributes this way.
To create a JavaFX instance from a Java object, use the object literal syntax with open and close curly braces. For example, the following script will print the date 24 hours from now.
Although you cannot initialize the Java object attributes, it is possible to define abstract function implementations within an object literal. The first example shows how to override the methods in a Java interface with JavaFX functions. For example, the Comparator
interface’s Java functions are
In JavaFX, these can be implemented within the object literal syntax:
This next example shows how to override a method in a Java class. This overrides the Java method:
The object literal declaration for this is
When declaring Java methods in JavaFX, the JavaFX syntax must be used, and the parameters declared using the mapped JavaFX type. For example, the Java method
public double getWidth()
is represented in JavaFX as
public override function getWidth() : Double
Likewise, the Java method
public void setSize(double width, double height)
is represented as a JavaFX function:
The second way to instantiate a Java object from JavaFX is to use the new
operator similar to the way it is used in Java, passing any required constructor arguments. Here is another way to create a Date
object that contains the time 24 hours from now. The variable, millis
, is passed to the constructor for Date
.
Notice the parentheses rather than the curly braces. Arguments are converted according to the rules outlined in the next section, Function Parameter and Return Mapping. Also, when using the new
operator, it is not possible to override any of the Java object’s methods. Of course, if the Java class is an interface or abstract class, it cannot be instantiated this way. It needs a subclass to implement the abstract methods.
JavaFX has ten basic types: Number
, Double
, Float
, Long
, Integer
, Short
, Byte
, Character
, Boolean
, and String
. The preferred types for normal use are Number
, Integer
, Boolean
, and String
. Unless you have a specific need for the other types, stick with the preferred types. All these types are represented by corresponding Java classes and therefore have access to the corresponding Java methods available to them. For example, the following illustrates use of the Character
class method, isWhiteSpace()
, and the Integer
class compareTo()
method.
Table 11.1 maps the JavaFX type to its corresponding Java type.
Table 11.1 JavaFX Types – Classes
JavaFX literals are also JavaFX objects, so it is possible to access their class’s respective methods directly from the literal. For example, the number literal 3.14 can be converted to an integer by calling its intValue()
method inherited from java.lang.Number
.
Likewise, the integer literal 4 can be used to get its bit count by invoking the bitCount()
method inherited from java.lang.Integer
.
When the basic JavaFX types are passed as parameters to Java methods or a Java type is being assigned to one of the JavaFX basic types, an attempt is made to convert between the two. The following eight tables, Tables 11.2 through 11.9, illustrate the conversion rules for each of the JavaFX basic types, including sequences. The first column is the JavaFX type, the second column is the Java type, the third column is the rule when the JavaFX type is passed as a parameter to a Java method call. The last column is the rule when a Java type is being assigned to a JavaFX type, either directly or as a result of a return type from a function or Java method.
For parameter conversion, if an automatic conversion is not supported, an alternative is presented in the Parameter Conversion column. Usually, the type needs to be converted to the Java Type using one of the xxxValue()
methods on the JavaFX object. For example, if the JavaFX type is a Number
, when trying to convert to the Java java.lang.Double
class in the Java method parameter, you need to invoke the doubleValue()
function on the JavaFX Number
class. This listing illustrates this.
In the Assignment column, the message, “possible loss of precision” means the compiler will issue a warning message that the conversion will lose some accuracy. If you want to avoid this warning message, the return type should be narrowed to the JavaFX type. For example, when converting a Java double to a JavaFX Long, use the longValue()
function on the returned type. For example:
Table 11.2 JavaFX – Java Type Conversion Mappings – Number/Float
Table 11.3 JavaFX – Java Type Conversion Mappings – Double
Table 11.4 JavaFX – Java Type Conversion Mappings – Long
Table 11.5 JavaFX – Java Type Conversion Mappings – Integer
Table 11.6 JavaFX – Java Type Conversion Mappings – Short
Table 11.7 JavaFX – Java Type Conversion Mappings – Byte
Table 11.8 JavaFX – Java Type Conversion Mappings – Character
Table 11.9 JavaFX – Java Type Conversion Mappings – Boolean, String Sequence
Sequences are converted to an array of the Java type—for example, the String
sequence
var authors = [ "Clarke", "Connors", "Bruno" ];
is passed as a Java String[]
array because authors
is a sequence of String
. Likewise, the integer sequence
var numbers = [ 2, 4, 6, 8, 10 ];
can be applied to Java type, int []
or Integer[]
. However, there is no automatic conversion from a sequence of integer to other types, like double[]
, float[]
, long[]
, and so on. To convert from one number type to the other, use a for
loop to convert to the desired type, and then use that result. For example, the numbers
integer sequence is converted to a sequence of type Double
using a for
loop, as shown in the following example.
All other JavaFX object types not listed in the Tables 11.2 through 11.9 are passed, unchanged, to and from the Java methods. For example, if you pass a javafx.geometry.Point2D
object to a Java method, it will still be a javafx.geometry.Point2D
object within the Java method. If the Java method returns a Java object, like java.util.HashMap
, it will still be a java.util.HashMap
in JavaFX.
When passing JavaFX objects to Java methods, interactions to those objects should be made via the javafx.reflect
package or via a JavaFX function. Direct manipulation of the JavaFX variables within a JavaFX object must be done via the JavaFX reflection framework. Another instance when the JavaFX reflection framework is required is when the Java code needs to create a JavaFX object. The javafx.reflect
package is actually written directly in Java and is safe to use from Java code assuming you are in the main JavaFX thread. The package javafx.reflect
is covered in more detail later in this chapter.
If you want to run JavaFX script source from your Java program, you need to use the Java Scripting API. The Java Scripting API, JSR-223, is a standard framework for running a script from Java code. Any scripting language can be used as long as it is JSR-223 compliant. Some examples of these supported languages are JavaScript, Groovy, Python, Ruby, and of course JavaFX script.
The simplest way to accomplish this is to use the javafx.util.FXEvaluator
class. This class is actually a Java class and can be safely used in a Java program. FXEvaluator
has a static method Object eval(String script)
that takes a JavaFX script as a string and returns the JavaFX object created within that script, if any. To run your application when using scripting, you must include the JavaFX compiler JAR, javafxc.jar
, in your classpath.
Let’s start with a simple Hello World example:
When run, this program produces the following output:
The script is just "println('hello world'),"
and this merely prints to the console. Because println
does not return anything, the returned object is null. So, let’s modify it a bit:
This now produces
Now let’s do something a bit more complicated, as depicted in Listing 11.1.
Listing 11.1 Java Scripting for JavaFX
This produces a returned object for Student
:
FXEvaluator
is a way to simply execute a JavaFX script and get a resulting object back. However, instead of hard coding “jim”, age 29, in the script, what if you want to pass in arguments to the script? FXEvaluator
does not provide a means for this. To add this kind of functionality, you need to directly work with the Java Scripting API.
To add bindings to a script, first, get the JavaFX script engine by creating a ScriptEngineManager
and using it to get the JavaFX script engine by either name, getEngineByName()
, or extension getEngineByExtension()
. In either case, the argument can be "javafx"
or "fx"
. This needs to be cast to a JavaFXScripEngine
.
Next, any global bindings may be bound to the script using the engine.put()
methods. In this example, we are adding name
and age
global bindings. The script has been modified to use these global bindings when the Student
class is instantiated and is shown in Listing 11.2.
Listing 11.2 Java Scripting for JavaFX – Global Bindings
The result of running this program produces the Student
with the values for name
and age
of “Eric” and 25, respectively.
Let’s say we want to reuse the Student
object and invoke methods on it. You do this by first compiling the object, setting the bindings, then creating an instance of the object. After we have the object instance, we can invoke methods on the Student
object.
First, let’s modify the script to add an instance function, getName()
, as shown in Listing 11.3.
Listing 11.3 Java Code – Sample JavaFX Script for Java Scripting API
Notice that since we will be using Bindings (SimpleBindings)
rather than the engine.put()
methods for adding bindings, we need to cast name
to String
and age
to Integer
within the script.
Next, the Java code compiles the JavaFX script string into a CompiledScript
object. Then we get the Student
object by calling eval()
with the bindings on the CompiledScript
. Lastly, we use the resulting Student
object to invoke the getName()
function. This is shown in Listing 11.4.
Listing 11.4 Java Code – Java Scripting API Invoking JavaFX Function
This prints out to the console:
Result = Eric
The ScriptEngine method, invokeMethod
, can also be used to call functions with arguments and provides a flexible way to manipulate the state of a JavaFX object. However, it is not possible to directly manipulate JavaFX instance variables through the Java Scripting API framework.
If you played around with the previous examples, you may have noticed that some JavaFX compilation errors print out to the console, and some do not. To overcome this, you need to add a diagnostic handler to collect the errors and then show them if a ScriptException
is encountered. The javax.tools.Diagnostic
Collector
class provides a means to do this.
First, create an instance of DiagnosticCollector
, then pass this to either the compile()
or eval()
methods on the JavaFXScriptEngine
instance.
Now, when a ScriptException
is thrown, detailed error messages are contained in the DiagnosticCollector
object. Listing 11.5 shows how to list all the error messages.
Listing 11.5 Java Code – Java Scripting API Error Handling
To illustrate this, if we take the Student
script and change the getName()
function to return the erroneous instance variable nameBogus
. Without diagnostic handling, we get the following fairly useless error message from ScriptException
.
However, with diagnostic handling, we get the far more informative message:
Java Scripting for JavaFX provides a powerful tool for running JavaFX Script from Java code. It allows you to evaluate scripts and get returned objects so that later you can invoke functions on them. It is limited in that you cannot directly manipulate instance variables. However, if used in conjunction with the JavaFX Reflection API, even this limitation can be overcome.
The JavaFX Reflection
package, javafx.reflect
, allows complete access to JavaFX objects from both Java and JavaFX code. The classes in javafx.reflect
are actually Java classes and can safely be used from Java programs. Nonetheless, you need to have the appropriate JavaFX SDK libraries in your classpath.
The first task to use the JavaFX Reflection
is to find an object’s class. You do this by getting the javafx.reflect.FXContext,
then using that to find the class reference. We are using javafx.reflect.FXLocal.Context
that implements FXContext,
because this class allows us to additionally mirror JavaFX objects, whereas FXContext
only allows mirroring of the JavaFX basic types. Mirroring provides a proxy (a level of indirection) so that the same API could potentially work with remote objects in a separate JVM. However, for now, the only implementation is on a local VM, hence the FXLocal
implementation.
FXContext
and the other javafx.reflect.FX
xxxxx
classes, like javafx.reflect
.FXClassType
and javafx.reflect.FXValue
, are abstract APIs, whereas FXLocal.Context
and other FXLocal.X
xxxxx
classes, like javafx.reflect
.FXLocal.ClassType
and javafx.reflect.FXLocal.Value
, are concrete implementations of the JavaFX Reflection API. These FXLocal
classes sit on top of Java reflection, and thus require the mirrored values and types to be in the same VM.
After you obtain the classRef
, you can either create an instance of the class or use an existing object of that type to manipulate the object’s state. To create a new instance, call newInstance()
on the classRef
.
Or, if you need to initialize some of the objects instance variables:
After the object instance is created, you can get instance variables via the FXClassType
getVariable()
methods and access the class functions via the getFunction()
methods. To set the x
and y
instance variables in javafx.geometry.Point2D
, you must first get the FXVarMember
for the x
and y
instance variables, and then set their respective values:
The context.mirrorOf()
function wrappers the value with a proxy that uses the local VM to handle the reflection.
If you have a Java method parameter that is a JavaFX object, you convert that to a FXLocal.ObjectValue
using context.mirrorOf
as shown in Listing 11.6.
Listing 11.6 JavaFX Reflection
To invoke functions, get the FXFunctionMember
object for the function, then invoke it using the instance obj
as shown in Listing 11.7.
Listing 11.7 JavaFX Reflection – Function Invocation
For parameterized functions, locate the function using the function name and parameter types. When invoking this kind of function, pass in the object as the first argument followed by the parameter objects wrapped in mirrors. This is shown in Listing 11.8.
Listing 11.8 JavaFX Reflection – Function Invocation with Parameters
Because the JavaFX Reflection API is written in Java, you can easily use the same classes from within a JavaFX script. Just change the format to JavaFX. Listing 11.9 shows an example of using the JavaFX Reflection API from JavaFX.
Listing 11.9 JavaFX Reflection – from JavaFX
It is easy to incorporate Java classes into JavaFX script. However, it helps to understand some of the basic rules for doing this. This chapter has provided you with the basics for interacting between the two environments. First, we discussed the inclusion of Java object within JavaFX script, then we discussed the Java Script API for JavaFX, and lastly we covered the JavaFX Reflection API.
Now you have the basics. In the next chapter, we cover JavaFX code recipes. After that, we are going to bring it all together in a JavaFX Sudoku application. Even the Sudoku application uses Java by incorporating an open source Java library from SourceForge to generate and solve the Sudoku puzzle.