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?
This method is called accessor. For example, if property name is firstName, your method name would be setFirstName() to write that property. This method is called mutator.
The JavaFX scene graph, which represents the graphical user interface of a JavaFX application, is not thread-safe and can only be accessed and modified from the UI thread also known as the JavaFX Application thread.
JavaFX properties are often used in conjunction with binding, a powerful mechanism for expressing direct relationships between variables. When objects participate in bindings, changes made to one object will automatically be reflected in another object. This can be useful in a variety of applications.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With