One of the big features I’ve known people have wanted for a long time (hey, I’ve wanted it too!) is support for returning a TableView back to its original, unsorted state after being sorted by the end user. In general the user interaction goes something like this:
- Click on a TableView column header once. Everything sorts in ascending order. Great!
- Click on the same column header again. Everything sorts in descending order. We’re on a roll here!
- Click on the same column again. The sort arrow disappears, and…….nothing 🙁
Of course, what should happen here is that the order of the items in the table should be reset back to their original order, from before the user ever clicked on anything. If you step behind the curtains with me for the briefest of moments, you’ll realise that the only way we can really do this is to of course keep a copy of the list in its original state (or a list of all the changes to the original list, such that we can unwind the changes later on). I never really wanted to do this, as you’re just setting yourself up for failure / pain / bugs / etc. What I always wanted to do was follow the wonderful GlazedLists approach from the Swing days, where the collections themselves became smarter, and the TableView remained mostly* inconsiderate of the type of collection given to it.
Originally, SortedList and FilteredList were targeted to JavaFX 2.0, but due to lack of time / engineers / etc, it had to be unfortunately scuttled from that release. Ever since then I’ve been waiting (and gently pestering) the Prague JavaFX team to reintroduce the functionality. Fortunately they relented and graciously spent the time to develop these collection classes for JavaFX 8.0. Whilst these collection classes were being defined, one of the major use cases was for their use in TableView, so it’s no surprise that the collections and TableView work really nicely together. However, until today I hadn’t actually taken the time to test that everything was working as expected, so when I stumbled upon an issue in our JavaFX Jira issue tracker, it gave me a chance to actually test it out to make sure everything works as expected. The tl;dr answer is “yes, it works great….now (and I’ll push the fixes to the repo soon)”.
So, what is the current approach to sorting in TableView (which is still applicable to those of you that don’t want to support returning to the unsorted state)? It looks a little something like this:
- Create a TableView
- Define the TableColumns
- Set the TableView items list to the applicable ObservableList instance
- Go and program something else
In other words, your items list will be directly sorted as the user interacts with the table columns. That is, the items list is both the model and the view.
With the introduction of SortedList, you have to do a little bit more work. Here are the steps you’ll want to take:
- Create a TableView
- Define the TableColumns
- Set the TableView items list to the applicable SortedList instance
- Bind the SortedList comparator property to the TableView comparator property
- Go and program something else
To those without eagle-like eye sight, it’s the third and fourth steps above that are a little different. What you’re doing is telling the SortedList to use the Comparator provided by the TableView, and this Comparator will either be a special comparator based on the currently selected table column(s), or it will be null (if no columns are in the sort order). If the comparator is null, the SortedList is designed to return to its unsorted state. Pretty neat, huh? 🙂
If following a five step process is too complex, here is some sample code you can refer to:
// create a SortedList based on the provided ObservableList SortedList sortedList = new SortedList(FXCollections.observableArrayList(2, 1, 3)); // create a TableView with the sorted list set as the items it will show final TableView<Integer> tableView = new TableView<>(sortedList); // bind the sortedList comparator to the TableView comparator sortedList.comparatorProperty().bind(tableView.comparatorProperty()); // Don't forget to define columns!
That’s it!
If you put a SortedList into a TableView, but forget to bind the comparator, you’ll find that nothing happens when you go to sort the TableView. This is because the TableView is updating its comparator but the SortedList isn’t listening.
I hope this has been informative! 🙂 Now it’s time for me to get back to squashing bugs 😎
* I say that TableView is mostly inconsiderate of the type of collection, but in reality it makes everyones day a little easier if I have the TableView smooth out some of the bumps. I know this will upset the purists, but I’m yet to find a way to make an omelet without breaking a few eggs – and my kitchen has the mess to prove it.
Glazedlists like functionality – cool!
Thanks for the interesting presentation of this cool feature.
But clearing the backed list of the sortedlist causes an ArrayIndexOutOfBoundsException…
any clue ?
Thanks
Did you have find the solution for this problem?
Yes, add items not to the SortedList, but to the list that is original source list.
I would like to know how to reset the tableview back to the top after repopulating the table. i.e. reset table so the first record is displayed
Thanks for the help, although I found one more problem in my code.
When I change the property in the column the tableview is currently sorted, the tableview doesn’t get sorted again. When I click the header of the column again it sorts the list.
I am using a SortedList and jdk 8 b121.
Thanks in advance!
@Lukas Leitinger
Seems to work for me on b129:
class SearchService extends Service<ObservableList> {
@Override
protected Task<ObservableList> createTask() {
return new Task<ObservableList>() {
@Override
protected ObservableList call() throws Exception {
Thread.sleep(1000 * 5);
return FXCollections.observableArrayList(
new Data("Jacob", "jacob.smith@example.com"),
new Data("Isabella", "isabella.johnson@example.com"),
new Data("Ethan", "ethan.williams@example.com"),
new Data("Emma", "emma.jones@example.com"),
new Data("Michael", "michael.brown@example.com")
);
}
};
}
{
setOnScheduled(e -> {
btnSearch.setDisable(true);
table.setItems(null);
});
setOnSucceeded(e -> {
SortedList sl = new SortedList((ObservableList) e.getSource().getValue()); // why cast needed? ???
table.setItems(sl);
sl.comparatorProperty().bind(table.comparatorProperty());
table.getSelectionModel().clearSelection();
//table.sort();
btnSearch.setDisable(false);
});
setOnFailed(e -> {
btnSearch.setDisable(false);
System.err.println(e);
});
}
}
If i do as you write, i cann’t add items to list, i have java.lang.UnsupportedOperationException