I have some complex Observable structures, which may or may not be bad ideas, but which are not the focus of this question.
The problem with those structures is that they generate a lot of invalidations of Observable
objects being displayed by the UI. As near as I can tell, when the JavaFX UI is displaying something, it registers a ChangeListener
on it, so any attempts to use lazy evaluation go out the window. That is, invalidating the observable seems to tell the UI that it has potentially changed, which causes the UI to immediately request it's value, forcing it to evaluate immediately.
So, I had the idea of deferring the invalidations via Platform.runLater()
.
I created a class called DeferredBinding
that delegates everything to a wrapped Binding
, except the invalidate()
method, which it defers to the JavaFX UI thread to be processed later. It seems to work... I can invalidate a hundred times and it only seems to actually process the invalidation once.
But, I've not seen this pattern before and I am afraid it might fall into the category of "nice try but bad idea".
So, question: is this a bad idea? I am particularly concerned of errors introduced into other Observable
objects that depend on the DeferredBinding
. Will they be fine once the Platform.runLater()
happens?
package com.myapp.SAM.model.datastructures;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.binding.Binding;
import javafx.beans.value.ChangeListener;
import javafx.collections.ObservableList;
/**
* Specialized binding that defers its invalidations to the JavaFX UI thread in a throttled manner. The idea being that, if invalidate() is called many times,
* it only basically happens once (when the UI thread gets to it).
*/
public class DeferredBinding<T> implements Binding<T> {
private static final Logger logger = Logger.getLogger(DeferredBinding.class.getName());
private final Binding<T> binding;
private final AtomicBoolean pendingInvalidation = new AtomicBoolean(false);
public DeferredBinding(Binding<T> binding) {
this.binding = binding;
}
@Override
public void addListener(ChangeListener<? super T> listener) {
binding.addListener(listener);
}
@Override
public void removeListener(ChangeListener<? super T> listener) {
binding.removeListener(listener);
}
@Override
public T getValue() {
return binding.getValue();
}
@Override
public void addListener(InvalidationListener listener) {
binding.addListener(listener);
}
@Override
public void removeListener(InvalidationListener listener) {
binding.removeListener(listener);
}
@Override
public boolean isValid() {
return binding.isValid();
}
/**
* Override logic for invalidate() method to defer invalidation to runLater. Throttle the invalidations so as not to floor the JavaFX UI thread with
* multiple calls
*/
@Override
public void invalidate() {
if (pendingInvalidation.getAndSet(true) == false) {
Platform.runLater(() -> {
// Signal that the UI is processing the pending invalidation, so any additional invalidations must schedule another update.
pendingInvalidation.set(false);
binding.invalidate();
});
}
}
@Override
public ObservableList<?> getDependencies() {
return binding.getDependencies();
}
@Override
public void dispose() {
binding.dispose();
}
}
5 statements of invalidation that you shouldn't say when trying to support to someone you care about. Home Book Now (954) 787-6800 What is Invalidation? 5 Things You Shouldn’t Say Validation doesn’t necessarily mean we agree with another’s subjective reality. Validation simply allows another person’s emotional state a space to exist.
Valid and Binding Obligation Sample Clauses. Valid and Binding Obligation . The Transaction Documents to which the Seller or the Depositor is a party constitute, and when executed by the Seller and the Depositor (if not previously) will constitute, the legal, valid and binding obligations of the Seller and the Depositor, as applicable,...
Others may invalidate unintentionally. The well-intentioned invalidators often defend that the goal is to help someone feel better or differently—to an emotion they judge as a more accurate, more valid one. If you’re the recipient of invalidating messages, know this: YOU’RE NOT CRAZY!
Validation doesn’t necessarily mean we agree with another’s subjective reality. Validation simply allows another person’s emotional state a space to exist. Validation is a critical communication tool and expression of love and acceptance in relationships.
I would not try to solve performance problems ahead of time. Measure your application to determine if you have a problem and then go ahead..
Let's assume you have a problem, there are many ways to solve your abstract problem. Three solutions come to my mind:
1
Coalesce updates (Platform.runLater()
) to prevent saturation of the FX event queue as you have done in your example.
And since you are just invalidating the binding you do not have to fear loosing a value on the way. So it seems (without knowing the full application) that this way should work.
2
Using the Node
s built-in behavior of marking regions dirty for (re-)layouting. At some point you will call javafx.scene.Parent.requestLayout()
(this is the "invalidation") which will at some point in the future call javafx.scene.Parent.layoutChildren()
to apply the dirty attributes to the region.
3
Using solution 2 in a different way: By using a virtualized layout container. The TableView
, TreeTableView
and ListView
all use the virtualized approach to update only the visible cells.
See com.sun.javafx.scene.control.skin.VirtualFlow
and com.sun.javafx.scene.control.skin.VirtualContainerBase
for some JDK examples, another example would be the Flowless API.
As you did not tell any specifics I can not guide you any further. But it should be clear, that your approach may very well work and there are other ways.
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