Search code examples
javafxfiltertableview

Filtering JFX TableView with multiple values


I am currently trying to filter data in my TableView using FilteredList with predicate. I have 2 ComboBoxes to filter the values.

My table contains Result. Each Result has a Student, that Student has a Classroom he belongs in. Those are the values I want to filter them by.

I have no issues when I filter data using only 1 ComboBox. However, I don't know how to filter it based on both at the same time. The only real solution I could come up with would be a lot of if statements in both changeListeners on each ComboBox and that seems like unneccessarily complicated way to do it.

The values in my TableView get displayed multiple times if I use following code.

  private void filterData(){
        ObservableList<Result> observableList = FXCollections.observableList(view.getResultTableView().getAllResultsList());
        FilteredList<Result> filteredData = new FilteredList<>(observableList, p -> true);

        view.getClassroomComboBox().getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {

            filteredData.setPredicate(result -> {

                if (newValue.equals(view.getAllClassroomsChoice()) || newValue.equals(result.getStudent().getClassroom())) {
                    return true;
                }
                else
                    return false;
            });

            if(newValue.equals(view.getAllClassroomsChoice())){
                view.getStudentComboBox().setAllStudents();
            }
            else{
                view.getStudentComboBox().setStudentsByClassroom((Classroom) newValue);
            }
        });

        view.getStudentComboBox().getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
            filteredData.setPredicate(result -> {

                if (newValue.equals(view.getAllClassroomsChoice()) || newValue.equals(result.getStudent())) {
                    return true;
                }
                else
                    return false;
            });
        });


        SortedList<Result> sortedData = new SortedList<>(filteredData);

        sortedData.comparatorProperty().bind(view.getResultTableView().comparatorProperty());

        view.getResultTableView().setItems(sortedData);

    }

Solution

  • First, don't recreate the data structure every time. Just update the predicate on the FilteredList.

    You can create predicates for each combo box and bind each to the value in the combo box. Then you can combine predicates using Predicate.and(...) and bind the predicate property of the filtered list to the result.

    Here is a similar example:

    import java.util.Arrays;
    import java.util.List;
    import java.util.function.Function;
    import java.util.function.Predicate;
    
    import javafx.application.Application;
    import javafx.beans.binding.Bindings;
    import javafx.beans.property.ObjectProperty;
    import javafx.beans.property.SimpleObjectProperty;
    import javafx.beans.property.SimpleStringProperty;
    import javafx.beans.property.StringProperty;
    import javafx.beans.value.ObservableValue;
    import javafx.collections.FXCollections;
    import javafx.collections.transformation.FilteredList;
    import javafx.geometry.Insets;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.control.ComboBox;
    import javafx.scene.control.TableColumn;
    import javafx.scene.control.TableView;
    import javafx.scene.control.TextField;
    import javafx.scene.layout.BorderPane;
    import javafx.scene.layout.HBox;
    import javafx.stage.Stage;
    
    public class MultipleFilterTableExample extends Application {
    
        @Override
        public void start(Stage primaryStage) {
            TableView<Person> table = new TableView<>();
            table.getColumns().add(column("Name", Person::nameProperty));
            table.getColumns().add(column("Email", Person::emailProperty));
            table.getColumns().add(column("Gender", Person::genderProperty));
    
            ComboBox<Person.Gender> genderFilterCombo = new ComboBox<>();
            genderFilterCombo.getItems().addAll(Person.Gender.values());
    
            TextField nameFilterField = new TextField();
    
            ObjectProperty<Predicate<Person>> nameFilter = new SimpleObjectProperty<>();
            ObjectProperty<Predicate<Person>> genderFilter = new SimpleObjectProperty<>();
    
            nameFilter.bind(Bindings.createObjectBinding(() -> 
                person -> person.getName().toLowerCase().contains(nameFilterField.getText().toLowerCase()), 
                nameFilterField.textProperty()));
    
    
            genderFilter.bind(Bindings.createObjectBinding(() ->
                person -> genderFilterCombo.getValue() == null || genderFilterCombo.getValue() == person.getGender(), 
                genderFilterCombo.valueProperty()));
    
            FilteredList<Person> filteredItems = new FilteredList<>(FXCollections.observableList(createData()));
            table.setItems(filteredItems);
    
            filteredItems.predicateProperty().bind(Bindings.createObjectBinding(
                    () -> nameFilter.get().and(genderFilter.get()), 
                    nameFilter, genderFilter));
    
            Button clear = new Button("Clear Filters");
            clear.setOnAction(e -> {
                genderFilterCombo.setValue(null);
                nameFilterField.clear();
            });
    
            HBox filters = new HBox(5, nameFilterField, genderFilterCombo, clear);
            filters.setPadding(new Insets(5));
            BorderPane root = new BorderPane(table, filters, null, null, null);
            Scene scene = new Scene(root, 600, 600);
            primaryStage.setScene(scene);
            primaryStage.show();
        }
    
        private List<Person> createData() {
            return Arrays.asList(
                    new Person("Jacob Smith", "[email protected]", Person.Gender.MALE),
                    new Person("Isabella Johnson", "[email protected]", Person.Gender.FEMALE),
                    new Person("Ethan Williams", "[email protected]", Person.Gender.MALE),
                    new Person("Emma Jones", "[email protected]", Person.Gender.FEMALE),
                    new Person("Michael Brown", "[email protected]", Person.Gender.MALE)
            );
        }
    
        private static <S,T> TableColumn<S,T> column(String title, Function<S, ObservableValue<T>> property) {
            TableColumn<S,T> col = new TableColumn<>(title);
            col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
            return col ;
        }
    
        public static class Person {
    
            public enum Gender {MALE, FEMALE }
    
            private final StringProperty name = new SimpleStringProperty();
            private final StringProperty email = new SimpleStringProperty() ;
            private final ObjectProperty<Gender> gender = new SimpleObjectProperty<>();
    
            public Person(String name, String email, Gender gender) {
                setName(name);
                setEmail(email);
                setGender(gender);
            }
    
            public final StringProperty emailProperty() {
                return this.email;
            }
    
            public final String getEmail() {
                return this.emailProperty().get();
            }
    
            public final void setEmail(final String email) {
                this.emailProperty().set(email);
            }
    
            public final ObjectProperty<Gender> genderProperty() {
                return this.gender;
            }
    
            public final Gender getGender() {
                return this.genderProperty().get();
            }
    
            public final void setGender(final Gender gender) {
                this.genderProperty().set(gender);
            }
    
            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 static void main(String[] args) {
            launch(args);
        }
    }