Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaFX Tableview with FilteredList (JDK 8) does not sort by column

I was playing around with the FilteredList class they have added back in to JDK8 and it feels a lot faster - appears to give performance closer to GlazedLists. However table column sorting does not seem to work at all when I use a FilteredList instead of a ObservableList.

There are no exceptions / stacktrace in the console.

Here are my instance members in my controller:

    private ObservableList<Film> masterData = FXCollections.observableArrayList();
    private FilteredList<Film> filteredData;

My controller init:

@FXML
void initialize() {
   ...   
   filmTable.setItems(filteredData);
   ...
}

Constructor:

public FilmBrowserController() {

   ...
   masterData.addAll(filmRepository.findAllSortedByTitle());
   filteredData = new FilteredList<>(masterData);
   filteredData.setPredicate(film -> true);
}

I have been filtering the list by just setting a new predicate when a filter TextField is changed:

filteredData.setPredicate(film ->
     film.getTitle().toLowerCase().contains(filterField.getText().toLowerCase()));

I am pretty green to JavaFX still - am I missing something pretty basic or is this likely to be a bug? I am using the JDK 8 downloaded here: https://jdk8.java.net/download.html Build b100.

Is it not possible to have a table which can be filtered but sorted by column at the same time?

edit:

Modified another javaFX table sample I found on stackoverflow, I added this line (74):

table.setItems(table.getItems().filtered(p -> p.getFirstName().startsWith("a")));

The full source:

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Callback;

import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Random;

public class TableSortPerformanceTest extends Application {

    public static final int INIT_LIST_SIZE = 100_000;

    @Override
    public void start(Stage stage) {
        Scene scene = new Scene(new StackPane());
        stage.setTitle("Table View Sample");
        stage.setWidth(550);
        stage.setHeight(550);

        final Label label = new Label("Address Book");
        label.setFont(new Font("Arial", 20));

        final TableView<Person> table = new TableView<Person>();
        table.setEditable(true);

        TableColumn<Person, String> firstNameCol = new TableColumn<Person, String>("First Name");
        firstNameCol.setMinWidth(100);
        firstNameCol.setCellValueFactory(
                new PropertyValueFactory<Person, String>("firstName"));
        firstNameCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Person,String>, ObservableValue<String>>() {
            @Override
            public ObservableValue<String> call(CellDataFeatures<Person, String> cdf) {
                return cdf.getValue().firstNameProperty();
            }
        });

        TableColumn<Person, String> lastNameCol = new TableColumn<Person, String>("Last Name");
        lastNameCol.setMinWidth(100);
        lastNameCol.setCellValueFactory(
                new PropertyValueFactory<Person, String>("lastName"));



        TableColumn<Person, String> emailCol = new TableColumn<Person, String>("Email");
        emailCol.setMinWidth(200);
        emailCol.setCellValueFactory(
                new PropertyValueFactory<Person, String>("email"));



        final Random random = new Random();
        for (int i = 0; i < INIT_LIST_SIZE; i++) {
            table.getItems().add(new Person(randomString(random), randomString(random), randomString(random)));
        }
        table.getColumns().addAll(Arrays.asList(firstNameCol, lastNameCol, emailCol));

        table.setItems(table.getItems().filtered(p -> p.getFirstName().startsWith("a")));

        long start = new Date().getTime();
//        Collections.sort(table.getItems());
        long end   = new Date().getTime();
        System.out.println("Took: " + (end - start));


        final TextField addFirstName = new TextField();
        addFirstName.setPromptText("First Name");
        addFirstName.setMaxWidth(firstNameCol.getPrefWidth());
        final TextField addLastName = new TextField();
        addLastName.setMaxWidth(lastNameCol.getPrefWidth());
        addLastName.setPromptText("Last Name");
        final TextField addEmail = new TextField();
        addEmail.setMaxWidth(emailCol.getPrefWidth());
        addEmail.setPromptText("Email");

        final Button addButton = new Button("Add");
        addButton.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent e) {
                String firstName = isEmpty(addFirstName.getText()) ? randomString(random) : addFirstName.getText();
                String lastName  = isEmpty(addLastName.getText())  ? randomString(random) : addLastName.getText();
                String email     = isEmpty(addEmail.getText())     ? randomString(random) : addEmail.getText();
                Person person = new Person(firstName, lastName, email);
                int idx = Collections.binarySearch(table.getItems(), person);
                if (idx < 0) {
                    idx = -idx - 1;
                }
                table.getItems().add(idx, person);
                table.getSelectionModel().select(idx);
                table.scrollTo(idx);

                addFirstName.clear();
                addLastName.clear();
                addEmail.clear();
            }
        });

        final HBox hb = new HBox(3);
        hb.getChildren().addAll(addFirstName, addLastName, addEmail, addButton);

        final VBox vbox = new VBox();
        vbox.setSpacing(5);
        vbox.setPadding(new Insets(10));
        vbox.getChildren().addAll(label, table, hb);

        ((StackPane) scene.getRoot()).getChildren().addAll(vbox);

        stage.setScene(scene);
        stage.show();
    }

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

    private boolean isEmpty(String string) {
        return (string == null || string.isEmpty());
    }

    private String randomString(Random random) {
        char[] chars = new char[20];
        for (int i = 0; i < 20; i++) {
            int nextInt = random.nextInt(26);
            nextInt += random.nextBoolean() ? 65 : 97;
            chars[i] = (char) nextInt;
        }
        return new String(chars);
    }

    public static class Person implements Comparable<Person> {

        private final StringProperty firstName;
        private final StringProperty lastName;
        private final StringProperty email;

        private Person(String fName, String lName, String email) {
            this.firstName = new SimpleStringProperty(fName);
            this.lastName = new SimpleStringProperty(lName);
            this.email = new SimpleStringProperty(email);
        }

        public String getFirstName() {
            return firstName.get();
        }

        public void setFirstName(String fName) {
            firstName.set(fName);
        }

        public StringProperty firstNameProperty() {
            return firstName ;
        }

        public String getLastName() {
            return lastName.get();
        }

        public void setLastName(String fName) {
            lastName.set(fName);
        }

        public StringProperty lastNameProperty() {
            return lastName ;
        }

        public String getEmail() {
            return email.get();
        }

        public void setEmail(String fName) {
            email.set(fName);
        }

        public StringProperty emailProperty() {
            return email ;
        }

        @Override
        public int compareTo(Person o) {
            return firstName.get().compareToIgnoreCase(o.getFirstName());
        }
    }
}

I have now created a ticket for this https://javafx-jira.kenai.com/browse/RT-32091

like image 728
Andrew B Avatar asked Jul 30 '13 22:07

Andrew B


People also ask

How do I sort a table in JavaFX?

The JavaFX TableView enables you to sort the rows in the TableView. There are two ways rows can be sorted. The first way is for the user to click on the TableColumn header cell (where the column title is displayed). This will sort the rows in the TableView after the values of that column.

How do you filter a TableView?

We can filter TableView content in two main ways – manually, or by using the FilteredList class JavaFX provides. In either case, we can update our search criteria by placing a ChangeListener on the search box TextField. This way, each time the user changes their search, the TableView is updated automatically.

How do I sort a table view?

passing in the TableColumn (s) by which you want the data sorted. To make the sort persist even as the items in the list change, create a SortedList and pass it to the table's setItems(...) method. (To change the items, you will still manipulate the underlying list.)


2 Answers

Turns out this is not yet supported (https://javafx-jira.kenai.com/browse/RT-32091) - but should be in future builds:

Martin Sladecek:

FilteredList is unmodifiable, so it cannot be sorted. You need to wrap it also in SortedList for this purpose. Short tutorial here: http://fxexperience.com/2013/08/returning-a-tableview-back-to-an-unsorted-state-in-javafx-8-0/

I'm changing this to tweak as I'm thinking about making FXCollections.sort() method to be TransformationList aware, so sorting of a FilteredList would be possible. As TableView is using this method, it should work immediately after this will be implemented.

Adding code example:

package com.test;

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Callback;

import java.util.*;

public class TableFilterTest extends Application {

    public static final int INIT_LIST_SIZE = 100_000;

    @Override
    public void start(Stage stage) {
        Scene scene = new Scene(new StackPane());
        stage.setTitle("Table View Sample");
        stage.setWidth(550);
        stage.setHeight(550);

        final Label label = new Label("Address Book");
        label.setFont(new Font("Arial", 20));

        final TableView<Person> table = new TableView<Person>();
        table.setEditable(true);

        TableColumn<Person, String> firstNameCol = new TableColumn<Person, String>("First Name");
        firstNameCol.setMinWidth(100);
        firstNameCol.setCellValueFactory(
                new PropertyValueFactory<Person, String>("firstName"));

        firstNameCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Person,String>, ObservableValue<String>>() {
            @Override
            public ObservableValue<String> call(CellDataFeatures<Person, String> cdf) {
                return cdf.getValue().firstNameProperty();
            }
        });

        TableColumn<Person, String> lastNameCol = new TableColumn<Person, String>("Last Name");
        lastNameCol.setMinWidth(100);
        lastNameCol.setCellValueFactory(
                new PropertyValueFactory<Person, String>("lastName"));

        TableColumn<Person, String> emailCol = new TableColumn<Person, String>("Email");
        emailCol.setMinWidth(200);
        emailCol.setCellValueFactory(
                new PropertyValueFactory<Person, String>("email"));


        final Random random = new Random();

        List<Person> personList = new ArrayList<>();
        for (int i = 0; i < INIT_LIST_SIZE; i++) {
            personList.add(new Person(randomString(random), randomString(random), randomString(random)));
        }

        ObservableList<Person> personLists = FXCollections.observableList(personList);
        table.getColumns().addAll(Arrays.asList(firstNameCol, lastNameCol, emailCol));


        final FilteredList<Person> filteredList  = personLists.filtered(p -> p.getFirstName().startsWith("a"));


        SortedList<Person> personSortedList = new SortedList<>(filteredList);

        table.setItems(personSortedList);
        personSortedList.comparatorProperty().bind(table.comparatorProperty());

        long start = new Date().getTime();
        long end   = new Date().getTime();
        System.out.println("Took: " + (end - start));

        final TextField addFirstName = new TextField();
        addFirstName.setPromptText("First Name");
        addFirstName.setMaxWidth(firstNameCol.getPrefWidth());
        final TextField addLastName = new TextField();
        addLastName.setMaxWidth(lastNameCol.getPrefWidth());
        addLastName.setPromptText("Last Name");
        final TextField addEmail = new TextField();
        addEmail.setMaxWidth(emailCol.getPrefWidth());
        addEmail.setPromptText("Email");

        final TextField filterText = new TextField();
        filterText.setMaxWidth(emailCol.getPrefWidth());
        filterText.setPromptText("Filter");
        filterText.textProperty().addListener((observable, oldValue, newValue) ->
                filteredList.setPredicate(p -> p.getFirstName().startsWith(filterText.getText()))
        );

        final Button addButton = new Button("Add");
        addButton.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent e) {
                String firstName = isEmpty(addFirstName.getText()) ? randomString(random) : addFirstName.getText();
                String lastName  = isEmpty(addLastName.getText())  ? randomString(random) : addLastName.getText();
                String email     = isEmpty(addEmail.getText())     ? randomString(random) : addEmail.getText();
                Person person = new Person(firstName, lastName, email);
                int idx = Collections.binarySearch(table.getItems(), person);
                if (idx < 0) {
                    idx = -idx - 1;
                }
                table.getItems().add(idx, person);
                table.getSelectionModel().select(idx);
                table.scrollTo(idx);

                addFirstName.clear();
                addLastName.clear();
                addEmail.clear();
            }
        });

        final HBox hb = new HBox(3);



        hb.getChildren().addAll(addFirstName, addLastName, addEmail, addButton, filterText);

        final VBox vbox = new VBox();
        vbox.setSpacing(5);
        vbox.setPadding(new Insets(10));
        vbox.getChildren().addAll(label, table, hb);

        ((StackPane) scene.getRoot()).getChildren().addAll(vbox);

        stage.setScene(scene);
        stage.show();

    }

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

    private boolean isEmpty(String string) {
        return (string == null || string.isEmpty());
    }

    private String randomString(Random random) {
        char[] chars = new char[20];
        for (int i = 0; i < 20; i++) {
            int nextInt = random.nextInt(26);
            nextInt += random.nextBoolean() ? 65 : 97;
            chars[i] = (char) nextInt;
        }
        return new String(chars);
    }

    public static class Person implements Comparable<Person> {

        private final StringProperty firstName;
        private final StringProperty lastName;
        private final StringProperty email;

        private Person(String fName, String lName, String email) {
            this.firstName = new SimpleStringProperty(fName);
            this.lastName = new SimpleStringProperty(lName);
            this.email = new SimpleStringProperty(email);
        }

        public String getFirstName() {
            return firstName.get();
        }

        public void setFirstName(String fName) {
            firstName.set(fName);
        }

        public StringProperty firstNameProperty() {
            return firstName ;
        }

        public String getLastName() {
            return lastName.get();
        }

        public void setLastName(String fName) {
            lastName.set(fName);
        }

        public StringProperty lastNameProperty() {
            return lastName ;
        }

        public String getEmail() {
            return email.get();
        }

        public void setEmail(String fName) {
            email.set(fName);
        }

        public StringProperty emailProperty() {
            return email ;
        }

        @Override
        public int compareTo(Person o) {
            return firstName.get().compareToIgnoreCase(o.getFirstName());
        }
    }
}
like image 24
Andrew B Avatar answered Sep 19 '22 15:09

Andrew B


To sort a TableView tableView via column headers, do this:

ObservableList<Film> masterData = // ... Some observable list
FilteredList<Film> filteredData = new FilteredList(masterData);
SortedList<Film>   sortableData = new SortedList<>(filteredData);
tableView.setItems(sortableData);
sortableData.comparatorProperty().bind(tableView.comparatorProperty());

To filter the contents of the TableView, simply:

filteredData.setPredicate(x -> x.getTitle().contains("The Walki"));

To live filter the contents based on a TextField textField:

textField.textProperty().addListener((observa,old,neo)->
     filteredData.setPredicate(x -> x.getTitle().contains(neo))
);
like image 160
NonlinearFruit Avatar answered Sep 19 '22 15:09

NonlinearFruit