When working with JavaFX properties most of us are interested in notifications about value changes. Getting notified about any of these changes can be realised by applying a ChangeListener
or an InvalidationListener
. We’re fine so far, aren’t we?
(In case of you’re not familiar with using JavaFX properties, start with reading Using JavaFX Properties and Binding at Oracle’s website.)
Have a look at the following example:
package propertyexample; import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; public class PropertyExample { private StringProperty value; public PropertyExample() { this.value = new SimpleStringProperty(this, "value"); } public StringProperty getValueProperty() { return this.value; } public final void setValue(String stringValue) { this.value.set(stringValue); } public final String getValue() { return this.value.get(); } public static void main(String[] args) { final PropertyExample example = new PropertyExample(); example.getValueProperty().addListener(new InvalidationListener() { @Override public void invalidated(Observable o) { StringProperty property = (StringProperty)o; System.out.println("current value: " + property.getValue()); } }); //assign String instance with a value of 'a' and fire InvalidationListener example.setValue(new String("a")); //assign String instance with a value of 'a' and nothing will happen example.setValue(new String("a")); } }
As you might have expected the applied InvalidationListener
will only fire once, since new String("a").equals(new String("a")) => true
. This is fine for most cases, but what when you’re interested in getting notified when any value is assigned and expect the InvalidationListener
firing everytime a value is assigned, even if the new value equals the previously assigned one. I spent much time and tried many different ways
- extending the
SimpleStringProperty
- implementing a new
StringProperty
and - fallback to good old listeners
All of these ways lead to a working solution, but the expense of time and lines of code is beyond any comparison with my finally working solution. This one is as simple as just exchanging the StringProperty
with an ObjectProperty<String>
. This works since value comparison and marking as invalid is implemented differently in SimpleStringProperty
and SimpleObjectProperty<String>
.
package propertyexample; import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; public class PropertyExample { private ObjectProperty<String> value; public PropertyExample() { this.value = new SimpleObjectProperty<String>(this, "value"); } public ObjectProperty<String> getValueProperty() { return this.value; } public final void setValue(String stringValue) { this.value.set(stringValue); } public final String getValue() { return this.value.get(); } public static void main(String[] args) { final PropertyExample example = new PropertyExample(); example.getValueProperty().addListener(new InvalidationListener() { @Override public void invalidated(Observable o) { ObjectProperty<String> property = (ObjectProperty<String>) o; System.out.println("current value: " + property.get()); } }); //assign String instance with a value of 'a' and fire InvalidationListener example.setValue(new String("a")); //assign String instance with a value of 'a' and fire InvalidationListener example.setValue(new String("a")); } }
Just a note: To get notified you have to assign different references. Assigning the same reference as shown in the following example only results in firing the InvalidationListener
once.
String a = new String("a"); example.setValue(a); example.setValue(a);
But this shouldn’t be a hurlde
Januar 21, 2013 um 5:39 am Uhr
Are you sure it is perfectly fine to use JavaFX classes outside JavaFX? (and outside JavaFX application thread)
Recently i found a blog where someone was using the properties like that and he had a NotOnJavaFxThread exception and was blaming JavaFX for this. But I think that this is a bad practice and certainly a bad example of using JavaFX.
Januar 21, 2013 um 8:53 am Uhr
I don’t know the other blog post, but in my personal opinion the usage of JavaFX should be limited to UI purposes since it’s a UI technology.
Using properties for model->view communication is perfectly fine. Sourcecode used in this blog post should be considered as example-code.
Januar 21, 2013 um 7:39 pm Uhr
I mean, you run this code outside FX Application thread. Yes, in this example it is not crucial. But certainly worth to mention.
Januar 21, 2013 um 8:01 pm Uhr
Absolutely! No modifications of scene graph content should occur from outside the ‘FX Application Thread’.
Januar 21, 2013 um 8:06 pm Uhr
To run on the JavaFX Application thread either
extend Application and run from start() {}
OR
Platform.runLater(new Runnable(){
public void run(){
// this executes on JavaFX Application thread.
// You can even update UI from here.
}
});
Januar 21, 2013 um 8:11 pm Uhr
And by the way, the tip about only one invalidation event was helpful for me. Nice blog, keep it rolling