2

I have a TableView with data changing asynchronously. The TableView has a sort and I'd like updated rows to be sorted according to the current sort. New rows added to the model are sorted correctly, but changing the data doesn't get reflected in the current sort.

Is the only solution to call TableView.sort() after each update to the data?

import javafx.application.Application;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class SortingItOut extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        ObservableList<Person> data = FXCollections.observableArrayList();
        Person c = new Person("C", 120);
        data.addAll(new Person("A", 100), new Person("B", 50), c, new Person("D", 25));

        TableColumn<Person, String> nameColumn = new TableColumn<>("Name");
        nameColumn.setCellValueFactory(param -> param.getValue().name);
        TableColumn<Person, Number> ageColumn = new TableColumn<>("Age");
        ageColumn.setCellValueFactory(param -> param.getValue().age);

        TableView<Person> table = new TableView<>(data);
        table.getColumns().addAll(nameColumn, ageColumn);
        table.getSortOrder().add(ageColumn);

        SortedList<Person> sortedList = new SortedList<>(data);
        sortedList.comparatorProperty().bind(table.comparatorProperty());

        table.setItems(sortedList);

        Button modify = new Button("modify C age");
        modify.setOnAction(e -> c.setAge(c.getAge() == 120 ? 75 : 120));

        Button add = new Button("add");
        add.setOnAction(e -> data.add(new Person("E", (int) (Math.random() * 100))));

        VBox box = new VBox(10);
        box.setAlignment(Pos.CENTER);

        box.getChildren().addAll(table, modify, add);
        Scene scene = new Scene(box);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public class Person {

        private final SimpleStringProperty name = new SimpleStringProperty("");
        private final SimpleIntegerProperty age = new SimpleIntegerProperty(0);

        public Person(String name, Integer age) {
            setName(name);
            setAge(age);
        }

        public String getName() {
            return name.get();
        }

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

        public void setAge(Integer age) {
            this.age.set(age);
        }

        public Integer getAge() {
            return age.get();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}
Alex Colomb
  • 170
  • 8
  • I think something like [this](http://stackoverflow.com/a/26734379/5151575) would work too. You can bind changes in the list to table `sort()` function. But it seems kinda overhead to me. Why exactly don't u want to use `sort()` inside `modify` button `onAction`? – Enigo Jan 19 '17 at 08:20
  • Have to agree with the previous comment. Using one line of code (`TableView.sort()`) to accomplish this seems like a good enough solution. – Jonatan Stenbacka Jan 19 '17 at 08:32
  • 1
    @Enigo, thank you. The solution you linked to works. I guess I'm somewhat surprised that the table will sort on new entries into the list, but not on updates to entries already in the list. The example above is contrived. Real examples would have many asynchronous updates to the data and I'd rather not have to call sort() after each one.. – Alex Colomb Jan 19 '17 at 14:14
  • @AlexColomb, cool that it worked for you) – Enigo Jan 19 '17 at 14:51
  • The table can't re-sort on changes in the underlying data unless the `SortedList` receives information that those changes have actually occurred. The extractor does exactly that: it causes the underlying list to fire events if the properties supplied by the extractor change. This one change is far better than manually calling `sort()` in the event handler, imo, because it will ensure the table stays sorted no matter how those data change. If you try to call `sort()` yourself any time the data change, the code rapidly becomes unmaintainable as you add more functionality later. – James_D Jan 19 '17 at 15:40
  • @Enigo Note that you don't have to "bind changes in the list to table `sort()` function": the `SortedList` already does that for you. Simply changing the initialization of the underlying list to one with an extractor is enough. – James_D Jan 19 '17 at 15:45
  • @James_D, thank you, good to know that) – Enigo Jan 19 '17 at 17:03

2 Answers2

1

The table cannot re-sort automatically when you change the values in existing elements, because you haven't provided any mechanism for the SortedList to "know" when those changes occur.

If you expose the JavaFX properties in your model class:

public class Person {

    private final StringProperty name = new SimpleStringProperty("");
    private final IntegerProperty age = new SimpleIntegerProperty(0);

    public Person(String name, Integer age) {
        setName(name);
        setAge(age);
    }

    public StringProperty nameProperty() {
        return name ;
    }

    public IntegerProperty ageProperty() {
        return age ;
    }

    public String getName() {
        return name.get();
    }

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

    public void setAge(Integer age) {
        this.age.set(age);
    }

    public Integer getAge() {
        return age.get();
    }
}

and create your underlying list with an extractor so that it fires events when the properties of interest change:

ObservableList<Person> data = FXCollections.observableArrayList(p -> 
    new Observable[] {p.nameProperty(), p.ageProperty()});

then the SortedList will receive events when the properties change, and will be able to reorder the elements "automatically" as required.

James_D
  • 201,275
  • 16
  • 291
  • 322
  • I know this is pretty much equivalent to the linked answer; following the discussion in comments on the OP I just wanted to clarify that creating the list with the extractor is the *only* change needed; you don't need any further binding or listeners on the list - the `SortedList` already implements all of that. – James_D Jan 19 '17 at 16:03
0

As pointed out by @Enigo, this solution will ensure the table has correct sorting after updates to properties within each of the list items.

Community
  • 1
  • 1
Alex Colomb
  • 170
  • 8