mrbela
mrbela

Reputation: 4647

JavaFX - multiple textfields should filter one tableview

I've written a little example:

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class MultipleFilterTextfields extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        VBox vbox = new VBox();

        TextField firstNameFilterTextField = new TextField();
        TextField lastNameFilterTextField = new TextField();

        TableView<Person> tableView = new TableView<>();

        ObservableList<Person> list = FXCollections.observableArrayList(new Person("Peter", "Schmidt"),
                new Person("Hans-Peter", "Schmidt"), new Person("Hans", "Mustermann"));
        FilteredList<Person> filterList = new FilteredList<>(list);
        tableView.setItems(filterList);

        TableColumn<Person, String> firstNameCol = new TableColumn<>("FirstName");
        TableColumn<Person, String> lastNameCol = new TableColumn<>("LastName");
        firstNameCol.setCellValueFactory(new PropertyValueFactory<>("firstName"));
        lastNameCol.setCellValueFactory(new PropertyValueFactory<>("lastName"));

        firstNameFilterTextField.textProperty().addListener((obsVal, oldValue, newValue) -> {
            filterList.setPredicate(person -> person.getFirstName().contains(newValue));
        });
        lastNameFilterTextField.textProperty().addListener((obsVal, oldValue, newValue) -> {
            filterList.setPredicate(person -> person.getLastName().contains(newValue));
        });

        tableView.getColumns().addAll(firstNameCol, lastNameCol);

        vbox.getChildren().addAll(firstNameFilterTextField, lastNameFilterTextField, tableView);

        Scene scene = new Scene(vbox, 250, 400);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public class Person {

        private String firstName;
        private String lastName;

        public Person(String firstName, String lastName) {
            super();
            this.firstName = firstName;
            this.lastName = lastName;
        }

        public String getFirstName() {
            return firstName;
        }

        public String getLastName() {
            return lastName;
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

It looks like this:

enter image description here

Above the tableView there are two TextFields, one to filter the firstName and the other to filter the lastName. Each of them filters the whole tableView:

If I type in the first field: "Hans", the table filters the two persons "Hans-Peter Schmidt" and "Hans Mustermann". If I type in "Schmidt" in the second one, it filters "Peter Schmidt" and "Hans-Peter Schmidt".

The problem is, that I can't filter (with the code above) for firstName AND lastName at the same time.

Do you have any ideas?

Thanks for your help!

Upvotes: 3

Views: 2461

Answers (1)

James_D
James_D

Reputation: 209235

How about

filteredList.predicateProperty().bind(Bindings.createObjectBinding(() ->
    person -> person.getFirstName().contains(firstNameFilterTextField.getText())
           && person.getLastName().contains(lastNameFilterTextField.getText()),

    firstNameFilterTextField.textProperty(),
    lastNameFilterTextField.textProperty()

));

instead of the two listeners on the text fields.

Bindings.createObjectBinding(...) takes a function generating the computed value, along with a list of any properties that the binding has to observe. In this case, we want to recompute if either of the text fields change, so the list of properties is the text properties of the two text fields.

The function has to return a predicate, so it is of the form () -> predicate. The predicate itself is a function mapping a Person to a boolean, so the predicate looks like person -> condition, and the overall form of the first parameter is

() -> person -> condition

Note you could still do this with two listeners:

    firstNameFilterTextField.textProperty().addListener((obsVal, oldValue, newValue) -> {
        filterList.setPredicate(person -> person.getFirstName().contains(firstNameFilterTextField.getText()) 
           && person.getLastName().contains(lastNameFilterTextField.getText()));
    });
    lastNameFilterTextField.textProperty().addListener((obsVal, oldValue, newValue) -> {
        filterList.setPredicate(person -> person.getFirstName().contains(firstNameFilterTextField.getText()) 
           && person.getLastName().contains(lastNameFilterTextField.getText()));
    });

but the single binding seems more elegant (to me).

Upvotes: 5

Related Questions