11
JavaFX and Java Technology

“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.

Classes

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

Image

When this is run, f.x is updated, but f.fxPointX is not updated, and there is no error or warning message.

Image

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.

JavaBeans:

Image

Please note that JavaFX triggers and JavaBeans have no built-in connection. If you update a bean property, it will not automatically update a bound JavaFX variable. The inverse is true too: if you update a JavaFX variable, it does not automatically update a JavaBean property. In Chapter 12, JavaFX Code Recipes, we present a way to do this, using Java property change listeners and JavaFX on replace 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:

Image

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

Image

Java Objects

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.

Developer Warning:

Image

The JavaFX compiler and runtime will allow you to enter initializers for Java object attributes, but the compiler and runtime will just silently ignore them. For example:

Image

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.

Image

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

Image

In JavaFX, these can be implemented within the object literal syntax:

Image

This next example shows how to override a method in a Java class. This overrides the Java method:

public String toString() {}

The object literal declaration for this is

Image

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:

Image

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.

Image

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.

Function Parameter and Return Mapping

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.

Image

Table 11.1 maps the JavaFX type to its corresponding Java type.

Table 11.1 JavaFX Types – Classes

Image

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.

Image

Likewise, the integer literal 4 can be used to get its bit count by invoking the bitCount() method inherited from java.lang.Integer.

Image

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.

Image

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:

Image

Table 11.2 JavaFX – Java Type Conversion Mappings – Number/Float

Image

Table 11.3 JavaFX – Java Type Conversion Mappings – Double

Image

Table 11.4 JavaFX – Java Type Conversion Mappings – Long

Image

Table 11.5 JavaFX – Java Type Conversion Mappings – Integer

Image

Table 11.6 JavaFX – Java Type Conversion Mappings – Short

Image

Table 11.7 JavaFX – Java Type Conversion Mappings – Byte

Image

Table 11.8 JavaFX – Java Type Conversion Mappings – Character

Image

Table 11.9 JavaFX – Java Type Conversion Mappings – Boolean, String Sequence

Image

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.

Image

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.

Developers Warning:

Image

If you are passing a JavaFX object to Java that is not automatically converted to a Java type as outlined in the preceding, it is important that the Java code not manipulate it without using the JavaFX Reflection API.

Never manipulate a JavaFX object in any thread other than the main JavaFX thread. Manipulating JavaFX objects outside of the main processing thread is not safe. A way to make sure JavaFX object changes are executed on the main JavaFX thread is presented at the end of this chapter.

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.

Java Scripting

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.

Basic Scripting Evaluation

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:

Image

When run, this program produces the following output:

Image

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:

Image

This now produces

Image

Now let’s do something a bit more complicated, as depicted in Listing 11.1.

Listing 11.1 Java Scripting for JavaFX

Image

This produces a returned object for Student:

Image

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.

Java Scripting API with Global Bindings

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

Image

Image

The result of running this program produces the Student with the values for name and age of “Eric” and 25, respectively.

Image

Java Scripting API with Compilation

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

Image

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

Image

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.

Java Scripting API with Error Handling

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.DiagnosticCollector 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.

Image

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

Image

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.

Image

However, with diagnostic handling, we get the far more informative message:

Image

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.

JavaFX Reflection

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.FXxxxxx classes, like javafx.reflect .FXClassType and javafx.reflect.FXValue, are abstract APIs, whereas FXLocal.Context and other FXLocal.Xxxxxx 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.

Image

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.

Image

Or, if you need to initialize some of the objects instance variables:

Image

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:

Image

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

Image

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

Image

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

Image

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

Image

Chapter Summary

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset