Reputation: 183
I have a class SimpleElement
which has a weight field and the second one has a list of SimpleElement
and a weight field which depends on the sum of weight of all other SimpleElement
s containing in the list. Any one has any idea how to do that by binding?
My code:
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
public class SimpleElement {
IntegerProperty weight;
public SimpleElement() {
weight = new SimpleIntegerProperty();
}
public int getWeight() {
return weight.get();
}
public void setWeight(int weight) {
this.weight.set(weight);
}
public IntegerProperty weightProperty() {
return weight;
}
}
and
import java.util.ArrayList;
import java.util.List;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
public class RootElement {
List<SimpleElement> elements;
IntegerProperty weight;
public RootElement() {
elements = new ArrayList<>();
weight = new SimpleIntegerProperty();
}
public void addelements(SimpleElement element) {
elements.add(element);
}
}
Upvotes: 2
Views: 7701
Reputation: 81
simply get the sum and bind the property to the sum. Therefore, any changes made to the sum will be observed by the binding property
Upvotes: 1
Reputation: 66
Crferreira's answer uses Fluent API to construct the chain of bindings, I find it hard to clean and maintain when objects can be removed or replaced. It's better to use Low-Level API.
Though a huge set of prebuilt binding stuff in JavaFX API, ListBinding will not be invalidated when one of its elements gets a new property value. So you have to create your IntegerBinding subclass, that listens to changes in list and rebinds to new properties itself.
Based on code from a similar answer.
import java.util.ArrayList;
import java.util.List;
import javafx.beans.binding.IntegerBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
class RootElement {
ObservableList<SimpleElement> elements = FXCollections.observableList(new ArrayList<SimpleElement>());
IntegerBinding totalWeight;
public RootElement() {
totalWeight = new SumOfWeightsForListOfSimpleElementsIntegerBinding(elements);
}
public void addElement(SimpleElement element) {
elements.add(element);
}
public void removeElement(SimpleElement element) {
elements.remove(element);
}
public Integer getWeigth() {
return totalWeight.getValue();
}
}
class SimpleElement {
IntegerProperty weight;
public SimpleElement() {
this(0);
}
public SimpleElement(Integer weight) {
this.weight = new SimpleIntegerProperty(weight);
}
public int getWeight() {
return weight.get();
}
public void setWeight(int weight) {
this.weight.set(weight);
}
public IntegerProperty weightProperty() {
return weight;
}
}
class SumOfWeightsForListOfSimpleElementsIntegerBinding extends IntegerBinding {
// Reference to our observable list
private final ObservableList<SimpleElement> boundList;
// Array of currently observed properties of elements of our list
private IntegerProperty[] observedProperties = {};
// Listener that has to call rebinding in response of any change in observable list
private final ListChangeListener<SimpleElement> BOUND_LIST_CHANGE_LISTENER
= (ListChangeListener.Change<? extends SimpleElement> change) -> {
refreshBinding();
};
SumOfWeightsForListOfSimpleElementsIntegerBinding(ObservableList<SimpleElement> boundList) {
this.boundList = boundList;
boundList.addListener(BOUND_LIST_CHANGE_LISTENER);
refreshBinding();
}
@Override
protected int computeValue() {
int i = 0;
for (IntegerProperty bp : observedProperties) {
i += bp.get();
}
return i;
}
@Override
public void dispose() {
boundList.removeListener(BOUND_LIST_CHANGE_LISTENER);
unbind(observedProperties);
}
private void refreshBinding() {
// Clean old properties from IntegerBinding's inner listener
unbind(observedProperties);
// Load new properties
List<IntegerProperty> tmplist = new ArrayList<>();
boundList.stream().map((boundList1) -> boundList1.weightProperty()).forEach((integerProperty) -> {
tmplist.add(integerProperty);
});
observedProperties = tmplist.toArray(new IntegerProperty[0]);
// Bind IntegerBinding's inner listener to all new properties
super.bind(observedProperties);
// Invalidate binding to generate events
// Eager/Lazy recalc depends on type of listeners attached to this instance
// see IntegerBinding sources
this.invalidate();
}
}
public class Main {
public static void main(String[] args) {
SimpleElement se1 = new SimpleElement(10);
SimpleElement se2 = new SimpleElement(20);
SimpleElement se3 = new SimpleElement(30);
RootElement root = new RootElement();
root.totalWeight.addListener((ObservableValue<? extends Number> observable, Number oldValue, Number newValue) -> {
System.out.println(newValue);
});
root.addElement(se1);
root.addElement(se2);
root.addElement(se3);
se1.setWeight(1000);
root.removeElement(se3);
}
}
It's sad that such a common task as monitoring a sum of element's properties in a list requires that ugly boilerplate.
Upvotes: 1
Reputation: 1248
The RootElement
class can be rewritten to create a binding to each SimpleElement
, adding them up:
public class RootElement {
List<SimpleElement> elements;
IntegerProperty weight;
NumberBinding binding;
public RootElement() {
elements = new ArrayList<>();
weight = new SimpleIntegerProperty();
}
public void addelements(SimpleElement element) {
elements.add(element);
if (binding == null) {
binding = element.weightProperty().add(0);
} else {
binding = binding.add(element.weightProperty());
}
weight.bind(binding);
}
public Integer getWeight() {
return weight.get();
}
public ReadOnlyIntegerProperty weightProperty() {
return weight;
}
}
Example of usage:
public static void main(String[] args) {
SimpleElement se1 = new SimpleElement();
SimpleElement se2 = new SimpleElement();
SimpleElement se3 = new SimpleElement();
RootElement root = new RootElement();
root.addelements(se1);
root.addelements(se2);
root.addelements(se3);
root.weightProperty().addListener(new ChangeListener<Number>() {
@Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
System.out.println(newValue);
}
});
se1.setWeight(3);
se2.setWeight(2);
se1.setWeight(4);
}
The execution of the code above produces:
3
5
6
Upvotes: 2