Javafx: Reduce events of checkable Listbox

54 Views Asked by At

I have a generic Javafx Listbox with checkable items (up to 20 items).

When I (de-)select an item, a property change is released to update other parts of the program. It works all fine so far.

But I have additionally 2 buttons to (de-)select all items at once. Also this working as expected, but I get an event for each item, which means that up to 20 property change events are fired, when one would be enough.

It is not a performance problem, not much is done on the events, but it is bad style.

Can anybody suggest a better solution?

    public class FilterBox extends AnchorPane {

        ListView<Item> listFilter;
        Button buttonAll;
        Button buttonNone;

        public FilterBox() {
            init();
            place();
            action();

            getChildren().addAll(listFilter, buttonAll, buttonNone);
            handleChange();
        }

        private void init() {
            listFilter = new ListView<>();

            buttonAll = new Button("All");
            buttonNone = new Button("None");
        }

        private void place() {
            setTopAnchor(listFilter, 0d);
            setBottomAnchor(listFilter, 40d);
            setLeftAnchor(listFilter, 0d);
            setRightAnchor(listFilter, 0d);

            setBottomAnchor(buttonAll, 5d);
            setLeftAnchor(buttonAll, 0d);
            buttonAll.setPrefWidth(75d);

            setBottomAnchor(buttonNone, 5d);
            setRightAnchor(buttonNone, 0d);
            buttonNone.setPrefWidth(75d);
            
        }

        private void action() {
            listFilter.setCellFactory(CheckBoxListCell.forListView((Item item) -> item.selectedProperty()));

            buttonAll.setOnAction((t) -> {
                changeAll(true);
            });

            buttonNone.setOnAction((t) -> {
                changeAll(false);
            });
        }

        private void changeAll(Boolean state) {
            for (Item i : listFilter.getItems()) {
                i.setSelected(state);
            }
        }

        private void setListener(Item item) {
            item.selectedProperty().addListener((o, ov, nv) -> {
                Trigger.setNewFilterEvent();
            });
        }

        private void handleChange() {
            HashSet<String> list = InputData.getSportList();
            for (String s : list) {
                Item item = new Item(s, true);
                listFilter.getItems().add(item);
                setListener(item);
            }
        }

        public static class Item {

            private final StringProperty name = new SimpleStringProperty();
            private final BooleanProperty selected = new SimpleBooleanProperty();

            public Item(String name, boolean on) {
                setName(name);
                setSelected(on);
            }

            public final StringProperty nameProperty() {
                return this.name;
            }

            public final String getName() {
                return this.nameProperty().get();
            }

            public final void setName(final String name) {
                this.nameProperty().set(name);
            }

            public final BooleanProperty selectedProperty() {
                return this.selected;
            }

            public final boolean isSelected() {
                return this.selectedProperty().get();
            }

            public final void setSelected(final boolean sel) {
                this.selectedProperty().set(sel);
            }

            @Override
            public String toString() {
                return getName();
            }
        }
    }
1

There are 1 best solutions below

5
Slaw On

Assuming Trigger.setNewFilterEvent() is what you want called only once when the "select all" or the "deselect all" actions are fired, then you can use a boolean flag for this.

public class FilterBox {

  private boolean ignoreIndividualChanges;
  // other fields omitted for brevity

  private void changeAll(boolean state) {
    ignoreIndividualChanges = true;
    try {
      for (Item i : listFilter.getItems()) {
        i.setSelected(state);
      }
      Trigger.setNewFilterEvent(); // fire one event
    } finally {
      ignoreIndividualChanges = false;
    }
  }

  private void setListener(Item item) {
    item.selectedProperty().addListener((obs, ov, nv) -> {
      if (!ignoreIndividualChanges) {
        Trigger.fireNewFilterEvent();
      }
    });
  }

  // other methods omitted for brevity
}

Also, note I changed the parameter type for changeAll from Boolean to boolean. There's no reason to use the reference type here, so you should stick with the primitive type.