Continued poking around JavaFX classes with Groovy, here are my findings when binding is applied to a node's attribute. The first thing you learn is that bound attributes are declared in a different way, so I had to tweak again the code presented on the last post, it turns out both the property's name and type are different now, as shown in the following "classic" HelloWorld example
Which when run should look like this

Inspecting the custom node with javap yields the following information
[aalmiray@localhost:javafx]$ javap HelloWorld.HelloWorldNode
Compiled from "HelloWorld.fx"
class HelloWorld$HelloWorldNode extends javafx.scene.CustomNode implements
HelloWorld$HelloWorldNode$Intf,com.sun.javafx.runtime.FXObject{
public final com.sun.javafx.runtime.location.ObjectVariable $str;
public static javafx.scene.Node$Intf create$impl(HelloWorld$HelloWorldNode$Intf);
public com.sun.javafx.runtime.location.ObjectVariable get$str();
public static void applyDefaults$str(HelloWorld$HelloWorldNode$Intf);
public void initialize$();
public static void addTriggers$(HelloWorld$HelloWorldNode$Intf);
public javafx.scene.Node$Intf create();
public HelloWorld$HelloWorldNode();
public HelloWorld$HelloWorldNode(boolean);
public static void userInit$(HelloWorld$HelloWorldNode$Intf);
public static void postInit$(HelloWorld$HelloWorldNode$Intf);
static {};
}
Compiled from "HelloWorld.fx"
class HelloWorld$HelloWorldNode extends javafx.scene.CustomNode implements
HelloWorld$HelloWorldNode$Intf,com.sun.javafx.runtime.FXObject{
public final com.sun.javafx.runtime.location.ObjectVariable $str;
public static javafx.scene.Node$Intf create$impl(HelloWorld$HelloWorldNode$Intf);
public com.sun.javafx.runtime.location.ObjectVariable get$str();
public static void applyDefaults$str(HelloWorld$HelloWorldNode$Intf);
public void initialize$();
public static void addTriggers$(HelloWorld$HelloWorldNode$Intf);
public javafx.scene.Node$Intf create();
public HelloWorld$HelloWorldNode();
public HelloWorld$HelloWorldNode(boolean);
public static void userInit$(HelloWorld$HelloWorldNode$Intf);
public static void postInit$(HelloWorld$HelloWorldNode$Intf);
static {};
}
Looks like ObjectVariable is responsible for handling the binding stuff, let's peek inside again with javap
[aalmiray@localhost:/tmp]$ javap com.sun.javafx.runtime.location.ObjectVariable
Compiled from "ObjectVariable.java"
public class com.sun.javafx.runtime.location.ObjectVariable extends
com.sun.javafx.runtime.location.AbstractVariable implements com.sun.javafx.runtime.location.ObjectLocation{
protected java.lang.Object $value;
protected java.lang.Object $default;
public static com.sun.javafx.runtime.location.ObjectVariable make();
public static com.sun.javafx.runtime.location.ObjectVariable makeWithDefault(java.lang.Object);
public static com.sun.javafx.runtime.location.ObjectVariable make(java.lang.Object);
public static com.sun.javafx.runtime.location.ObjectVariable make(boolean, com.sun.javafx.runtime.location.ObjectBindingExpression, com.sun.javafx.runtime.location.Location[]);
public static com.sun.javafx.runtime.location.ObjectVariable make(com.sun.javafx.runtime.location.ObjectBindingExpression, com.sun.javafx.runtime.location.Location[]);
public static com.sun.javafx.runtime.location.ObjectVariable make(java.lang.Object, boolean, com.sun.javafx.runtime.location.ObjectBindingExpression, com.sun.javafx.runtime.location.Location[]);
public static com.sun.javafx.runtime.location.ObjectVariable make(java.lang.Object, com.sun.javafx.runtime.location.ObjectBindingExpression, com.sun.javafx.runtime.location.Location[]);
public static com.sun.javafx.runtime.location.ObjectVariable makeBijective(com.sun.javafx.runtime.location.ObjectLocation);
protected com.sun.javafx.runtime.location.ObjectVariable();
protected com.sun.javafx.runtime.location.ObjectVariable(java.lang.Object);
protected com.sun.javafx.runtime.location.ObjectVariable(java.lang.Object, boolean, com.sun.javafx.runtime.location.ObjectBindingExpression, com.sun.javafx.runtime.location.Location[]);
protected com.sun.javafx.runtime.location.ObjectBindingExpression
makeBindingExpression(com.sun.javafx.runtime.location.ObjectLocation);
public java.lang.Object get();
protected java.lang.Object replaceValue(java.lang.Object);
public java.lang.Object set(java.lang.Object);
public void setDefault();
public void update();
public boolean isNull();
protected com.sun.javafx.runtime.location.AbstractBindingExpression
makeBindingExpression(com.sun.javafx.runtime.location.ObjectLocation);
public void addChangeListener(com.sun.javafx.runtime.location.ObjectChangeListener);
}
Compiled from "ObjectVariable.java"
public class com.sun.javafx.runtime.location.ObjectVariable extends
com.sun.javafx.runtime.location.AbstractVariable implements com.sun.javafx.runtime.location.ObjectLocation{
protected java.lang.Object $value;
protected java.lang.Object $default;
public static com.sun.javafx.runtime.location.ObjectVariable make();
public static com.sun.javafx.runtime.location.ObjectVariable makeWithDefault(java.lang.Object);
public static com.sun.javafx.runtime.location.ObjectVariable make(java.lang.Object);
public static com.sun.javafx.runtime.location.ObjectVariable make(boolean, com.sun.javafx.runtime.location.ObjectBindingExpression, com.sun.javafx.runtime.location.Location[]);
public static com.sun.javafx.runtime.location.ObjectVariable make(com.sun.javafx.runtime.location.ObjectBindingExpression, com.sun.javafx.runtime.location.Location[]);
public static com.sun.javafx.runtime.location.ObjectVariable make(java.lang.Object, boolean, com.sun.javafx.runtime.location.ObjectBindingExpression, com.sun.javafx.runtime.location.Location[]);
public static com.sun.javafx.runtime.location.ObjectVariable make(java.lang.Object, com.sun.javafx.runtime.location.ObjectBindingExpression, com.sun.javafx.runtime.location.Location[]);
public static com.sun.javafx.runtime.location.ObjectVariable makeBijective(com.sun.javafx.runtime.location.ObjectLocation);
protected com.sun.javafx.runtime.location.ObjectVariable();
protected com.sun.javafx.runtime.location.ObjectVariable(java.lang.Object);
protected com.sun.javafx.runtime.location.ObjectVariable(java.lang.Object, boolean, com.sun.javafx.runtime.location.ObjectBindingExpression, com.sun.javafx.runtime.location.Location[]);
protected com.sun.javafx.runtime.location.ObjectBindingExpression
makeBindingExpression(com.sun.javafx.runtime.location.ObjectLocation);
public java.lang.Object get();
protected java.lang.Object replaceValue(java.lang.Object);
public java.lang.Object set(java.lang.Object);
public void setDefault();
public void update();
public boolean isNull();
protected com.sun.javafx.runtime.location.AbstractBindingExpression
makeBindingExpression(com.sun.javafx.runtime.location.ObjectLocation);
public void addChangeListener(com.sun.javafx.runtime.location.ObjectChangeListener);
}
I have highlighted some interesting methods, for now we will rely on get/set then we will come back to addChangeListener which looks like our way into listening to binding events. Here is the updated Groovy code capable of reading/writing bound and unbound properties of a JavaFX class
There are some extra javafx jars needed this time in order to run the example, given that HelloWorld is now using GUI classes, the following command should do the trick
[aalmiray@localhost:javafx]$ groovy -cp /usr/local/javafx/lib/shared/javafxrt.jar:/usr/local/javafx/lib/desktop/javafxgui.jar:/usr/local/javafx/lib/desktop/Scenario.jar:. GHelloWorld.groovy
Groovy
Groovy
So far so good, now let's device a way to register an ObjectChangeListener with the str attribute, which means we need to access the 'raw' attribute (not it's value) as provided by the meta getProperty() impl; let's add an attribute() method to all FXObject
// append after previous groovy code FXObject.metaClass.attribute = { String name -> def clazz = delegate.getClass() def mc = clazz.metaClass def metaProperty = mc.getMetaProperty(name) if(!metaProperty) metaProperty = mc.getMetaProperty("\$$name") if(!metaProperty) metaProperty = mc.getMetaProperty("\$${clazz.name.replace('.','\$')}\$$name") if(metaProperty && AbstractVariable.isAssignableFrom(metaProperty.type)) return metaProperty.getProperty(delegate) throw new MissingPropertyException(name,clazz) } def str = hw.attribute('str') str.set("JavaFX") println hw.strNotice that we grab a reference to the str property, change its value and then print it using property access as we previously did, we should see 'JavaFX' on the last outputed line when executing GHelloWorld.groovy
[aalmiray@localhost:javafx]$ groovy -cp /usr/local/javafx/lib/shared/javafxrt.jar:/usr/local/javafx/lib/desktop/javafxgui.jar:/usr/local/javafx/lib/desktop/Scenario.jar:. GHelloWorld.groovy
Groovy
JavaFX
Groovy
JavaFX
Excellent! now let's listen to change notifications when str's value changes, we'll need a subclass of ObjectChangeListener as it is an abstract class
[aalmiray@localhost:/tmp]$ javap com.sun.javafx.runtime.location.ObjectChangeListener
Compiled from "ObjectChangeListener.java"
public abstract class com.sun.javafx.runtime.location.ObjectChangeListener extends java.lang.Object implements com.sun.javafx.runtime.util.Linkable{
public com.sun.javafx.runtime.location.ObjectChangeListener();
public com.sun.javafx.runtime.location.ObjectChangeListener getNext();
public void setNext(com.sun.javafx.runtime.location.ObjectChangeListener);
public com.sun.javafx.runtime.location.AbstractVariable getHost();
public void setHost(com.sun.javafx.runtime.location.AbstractVariable);
public abstract void onChange(java.lang.Object, java.lang.Object);
public void setHost(java.lang.Object);
public void setNext(java.lang.Object);
public java.lang.Object getHost();
public java.lang.Object getNext();
}
Compiled from "ObjectChangeListener.java"
public abstract class com.sun.javafx.runtime.location.ObjectChangeListener extends java.lang.Object implements com.sun.javafx.runtime.util.Linkable{
public com.sun.javafx.runtime.location.ObjectChangeListener();
public com.sun.javafx.runtime.location.ObjectChangeListener getNext();
public void setNext(com.sun.javafx.runtime.location.ObjectChangeListener);
public com.sun.javafx.runtime.location.AbstractVariable getHost();
public void setHost(com.sun.javafx.runtime.location.AbstractVariable);
public abstract void onChange(java.lang.Object, java.lang.Object);
public void setHost(java.lang.Object);
public void setNext(java.lang.Object);
public java.lang.Object getHost();
public java.lang.Object getNext();
}
Only one method needs to be implemented, this is when we usually go for the anonymous class definition strategy, after all it is a one shot class because we are exploring the API, you're probably aware that Groovy does not support anonymous class definitions (yet), but we have an alternative: coercing a Closure or Map into interfaces of classes, so let's try that option
And the resulting output
[aalmiray@localhost:javafx]$ groovy -cp /usr/local/javafx/lib/shared/javafxrt.jar:/usr/local/javafx/lib/desktop/javafxgui.jar:/usr/local/javafx/lib/desktop/Scenario.jar:. GHelloWorld.groovy
Groovy
JavaFX
str changed from JavaFX to Java
Java
Groovy
JavaFX
str changed from JavaFX to Java
Java
Next: find a way to embed JavaFX UI components into a SwingBuilder instance (this might be trickier than the previous examples)
Keep on Groovying!