I want to use JavaFX properties for UI binding, but I don't want them in my model classes (see Using javafx.beans properties in model classes). My model classes have getters and setters, and I want to create properties based on those. For example, assuming an instance bean
with methods String getName()
and setName(String name)
, I would write
SimpleStringProperty property = new SimpleStringProperty(bean, "name")
expecting that property.set("Foobar")
would trigger a call to bean.setName
. But this doesn't seem to work. What am I missing?
The Simple*Property
classes are full, standalone implementations of their corresponding Property
abstract classes, and do not rely on any other object. So, for example, SimpleStringProperty
contains a (private) String
field itself which holds the current value of the property.
The parameters to the constructor you showed:
new SimpleStringProperty(bean, "name")
are:
bean
: the bean to which the property belongs, if anyname
: the name of the propertyThe bean
can be useful in a ChangeListener
's changed(...)
method as you can retrieve the "owning bean" of the property that changed from the property itself. The name
can be used similarly (if you have the same listener registered with multiple properties, you can figure out which property changed: though I never use this pattern).
So a typical use of a SimpleStringProperty
as an observable property of an object looks like:
public class Person {
private final StringProperty firstName
= new SimpleStringProperty(this, "firstName");
public final String getFirstName() {
return firstName.get();
}
public final void setFirstName(String firstName) {
this.firstName.set(firstName);
}
public StringProperty firstNameProperty() {
return firstName ;
}
// ... other properties, etc
}
The functionality you are looking for: to wrap an existing Java Bean style property in a JavaFX observable property is implemented by classes in the javafx.beans.property.adapter
package. So, for example, you could do
StringProperty nameProperty = new JavaBeanStringPropertyBuilder()
.bean(bean)
.name("name")
.build();
Calling
nameProperty.set("James");
with this setup will effectively cause a call to
bean.setName("James");
If the bean supports PropertyChangeListener
s, the JavaBeanStringProperty
will register a PropertyChangeListener
with the bean. Any changes to the name
property of the Java Bean will be translated by the JavaBeanStringProperty
into JavaFX property changes. Consequently, if the underlying JavaBean supports PropertyChangeListener
s, then changes to the bean via
bean.setName(...);
will result in any ChangeListener
s (or InvalidationListener
s) registered with the JavaBeanStringProperty
being notified of the change.
So, for example, if the Bean class is
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
public class Bean {
private String name ;
private final PropertyChangeSupport propertySupport ;
public Bean(String name) {
this.name = name ;
this.propertySupport = new PropertyChangeSupport(this);
}
public Bean() {
this("");
}
public String getName() {
return name ;
}
public String setName(String name) {
String oldName = this.name ;
this.name = name ;
propertySupport.firePropertyChange("name", oldName, name);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
propertySupport.addPropertyChangeListener(listener);
}
}
Then the following code:
Bean bean = new Bean();
StringProperty nameProperty() = new JavaBeanStringPropertyBuilder()
.bean(bean)
.name("name")
.build();
nameProperty().addListener((obs, oldName, newName) -> System.out.println("name changed from "+oldName+" to "+newName));
bean.setName("James");
System.out.println(nameProperty().get());
will produce the output:
name changed from to James
James
If the JavaBean does not support PropertyChangeListener
s, then changes to the bean via bean.setName(...)
will not propagate to ChangeListener
s or InvalidationListener
s registered with the JavaBeanStringProperty
.
So if the bean is simply
public class Bean {
public Bean() {
this("");
}
public Bean(String name) {
this.name = name ;
}
private String name ;
public String getName() {
return name ;
}
public void setName(String name) {
this.name = name ;
}
}
The JavaBeanStringProperty would have no way to observe the change, so the change listener would never be invoked by a call to bean.setName()
. So the test code above would simply output
James