bazi
bazi

Reputation: 1876

bindings : count number of true booleanproperty in an observable list

i have this TableView of tasks that has a column of checkboxes. i've managed to extract the column values using stream().map(). i want to count the true values in that list.

ObservableList<BooleanProperty> list = FXCollections.observableArrayList(tasksTable.getItems().stream().map(t -> t.completedProperty()).collect(Collectors.toList()));

and another integer property

SimpleIntegerProperty count = new SimpleIntegerProperty(0);

i want this count value to be updated to the total number of true values in the list. how can i do this. i tried binding it like this

IntegerBinding count = Bindings.createIntegerBinding(() -> {
    return (int)list.stream().filter(done -> done.get()).count();
}, list);

and added a listener to monitor for changes.

count.addListener((o, oldValue, newValue) -> {
    System.out.println('value changed ' + newValue);
});

but its not working... nothing gets printed when i click on any checkbox...

Upvotes: 0

Views: 902

Answers (2)

fabian
fabian

Reputation: 82491

You binding doesn't update, if the BooleanPropertys in the list change, only when the list itself is modified. You can specify conditions that trigger update changes using the correct observableArrayList method.

Example

// update triggers every time a BooleanProperty in the list is changed
ObservableList<BooleanProperty> list = FXCollections.observableArrayList((Callback<BooleanProperty, Observable[]>) e -> new Observable[] {e});

list.add(new SimpleBooleanProperty(false));
IntegerBinding trueNum = Bindings.createIntegerBinding(() -> (int) list.stream().map(BooleanProperty::get).filter(b -> b).count(), list);

trueNum.addListener((a, b, c) -> {
    System.out.println(c);
});

System.out.println(trueNum.get());
list.get(0).set(true);
list.add(new SimpleBooleanProperty(false));
list.add(new SimpleBooleanProperty(true));
list.get(2).set(false);

The output is

0
1
2
1

This has the drawback of reevaluating the whole list every time one of the properties changes or the list is modified. You can do this more efficiently, if you use a ListChangeListener and register change listeners to all of the properties contained in the list:

ObservableList<BooleanProperty> list = FXCollections.observableArrayList();

...

SimpleIntegerProperty count = new SimpleIntegerProperty();

ChangeListener<Boolean> changeListener = (observable, oldValue, newValue) -> {
    // increment or decrement if value was changed to true or false respecively
    count.set(count.get()+ (newValue ? 1 : -1));
};

// get initial value and register change listeners to elements of list
int currentCount = 0;
for (BooleanProperty p : list) {
    if (p.get()) {
        currentCount++;
    }
    p.addListener(changeListener);
}
count.set(currentCount);

list.addListener((ListChangeListener.Change<? extends BooleanProperty> c) -> {
    int modification = 0;
    while (c.next()) {
        // remove listeners from all properties removed in change
        // and calculate update of value
        for (BooleanProperty p : c.getRemoved()) {
            p.removeListener(changeListener);
            if (p.getValue()) {
                modification--;
            }
        }
        // add listeners from all properties added in change
        // and calculate update of value
        for (BooleanProperty p : c.getAddedSubList()) {
            p.addListener(changeListener);
            if (p.getValue()) {
                modification++;
            }
        }
    }
    // update count
    count.set(count.get()+modification);
});

Upvotes: 2

Denis Kokorin
Denis Kokorin

Reputation: 895

Your lambda is called only once. You specify dependency list but uses tasksTable.getItems() In lambda-extractor you should use only values you specify as dependencies.

Also take a look at EasyBind library.

Upvotes: 0

Related Questions