As far as I know there is no built in way to do this simply. However, there's a couple of ways you could do this. The most(?) efficient, but more complicated way would be to listen to the ObservableSet
for additions/removals, observe any current DoubleProperty
elements, and modify the total
property yourself.
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.beans.value.WeakChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
import javafx.collections.SetChangeListener;
public class SomeClass {
private final ReadOnlyDoubleWrapper total = new ReadOnlyDoubleWrapper(this, "total");
private void setTotal(double total) { this.total.set(total); }
public final double getTotal() { return total.get(); }
public final ReadOnlyDoubleProperty totalProperty() { return total.getReadOnlyProperty(); }
private final ObservableSet<DoubleProperty> propertySet = FXCollections.observableSet();
private final ChangeListener<Number> elementListener = this::elementValueChanged;
private final WeakChangeListener<Number> weakElementListener =
new WeakChangeListener<>(elementListener);
public SomeClass() {
propertySet.addListener(this::propertySetChanged);
}
private void propertySetChanged(SetChangeListener.Change<? extends DoubleProperty> change) {
if (change.wasRemoved()) {
change.getElementRemoved().removeListener(weakElementListener);
setTotal(getTotal() - change.getElementRemoved().get());
}
if (change.wasAdded()) {
change.getElementAdded().addListener(weakElementListener);
setTotal(getTotal() + change.getElementAdded().get());
}
}
private void elementValueChanged(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue) {
setTotal(getTotal() - oldValue.doubleValue() + newValue.doubleValue());
}
}
Here the SetChangeListener
, whose value is a method reference to propertySetChanged
, watches for any changes to the ObservableSet
. When a DoubleProperty
is added it adds said property's value to the current total. When a DoubleProperty
is removed it subtracts said property's value from the current total. This listener also adds or removes a ChangeListener
to or from the DoubleProperty
when it is added or removed from the ObservableSet
, respectively.
The ChangeListener
, whose value is a method reference to elementValueChanged
, updates the total
property when the value of any DoubleProperty
changes. It does this by first subtracting the old value and then adding the new value to the current total. It is actually the WeakChangeListener
, which wraps the original ChangeListener
, that is added or removed. This helps avoid potential memory leaks. Remember to maintain a strong reference to the original ChangeListener
when using WeakChangeListener
otherwise the original ChangeListener
may be garbage collected too soon.
A second option is to rebuild a binding every time the ObservableSet
is invalidated and then bind the total
property to said binding.
import javafx.beans.Observable;
import javafx.beans.binding.DoubleExpression;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
public class SomeClass {
private final ReadOnlyDoubleWrapper total = new ReadOnlyDoubleWrapper(this, "total");
private void setTotal(double total) { this.total.set(total); }
public final double getTotal() { return total.get(); }
public final ReadOnlyDoubleProperty totalProperty() { return total.getReadOnlyProperty(); }
private final ObservableSet<DoubleProperty> propertySet = FXCollections.observableSet();
public SomeClass() {
propertySet.addListener(this::propertySetInvalidated);
}
private void propertySetInvalidated(Observable observable) {
if (propertySet.isEmpty()) {
total.unbind();
setTotal(0.0);
} else if (propertySet.size() == 1) {
total.bind(propertySet.iterator().next());
} else {
DoubleExpression sum = null;
for (DoubleProperty property : propertySet) {
sum = (sum != null) ? sum.add(property) : property;
}
total.bind(sum);
}
}
}
In this case we add an InvalidationListener
to the ObservableSet
. This listener will be invoked whenever an element(s) is added to or removed from the ObservableSet
. When this happens 1 of 3 things will happen:
- If the
ObservableSet
is now empty unbind the total
property and set it to zero.
- This is a special case to deal with no elements
- If the
ObservableSet
now only contains a single element simply bind the total
property to said element.
- Another special case dealing with a single element. It stops us from creating unnecessary objects and preforming unnecessary computations that would happen if we just skipped to the third branch.
- Otherwise create one big binding that calculates the sum and then bind
total
to that binding.
- This branch uses
DoubleExpression.add(ObservableNumberValue)
. The resulting DoubleBinding
from that method call will update whenever one of the two observables change. This is reduced into a single DoubleExpression
which we then bind the total
property to.
This second option will be less efficient because it requires iterating the entire ObservableSet
every time. It also potentially leads to a lot of DoubleBinding
objects being created. However, you may find it simpler to code/understand and the performance hit may not be significant enough for your application.