Wadii Slatnia
Wadii Slatnia

Reputation: 183

JavaFX: How to bind multiple properties in a list?

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 SimpleElements 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

Answers (3)

AExplosion
AExplosion

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

mclaudt
mclaudt

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

Crferreira
Crferreira

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

Related Questions