How to use descending sorting first when clicking TableView column header?

891 Views Asked by At

Javafx TableView sorting uses ascending order first when clicking on a column header, then descending order on second click. I have a table with sports team data with columns for wins, losses etc. Wins should be sorted by default in descending order and losses in ascending order so the best team in each category is always on top when clicking once on a header.

I have tried sortType="DESCENDING" in the fxml file and setSortType(TableColumn.SortType.DESCENDING) in code with no effect.

2

There are 2 best solutions below

0
On

In my use case the secondary sorting option is more of a hinderance. The solution I came up with only allows sorting a column in one way, clicking the header toggles between sorted and not used for sorting.

Only allowing descending sorting is simpler and only requires a listener to the table sortOrder. Only allowing ascending sorting however requires adding a listener to the column sortTypeProperty and for multisorting using the shift-key to work correctly the correct sorting must also be forced in the table sortOrder listener.

columnLosses.sortTypeProperty().addListener(o -> {
    if (columnLosses.getSortType().equals(TableColumn.SortType.DESCENDING)) {
        table.getSortOrder().remove(columnLosses);
    }
});
table.getSortOrder().addListener((Observable o) -> {
    if (table.getSortOrder().contains(columnWins)) {
        columnWins.setSortType(TableColumn.SortType.DESCENDING);
    }
    if (columnLosses.getSortType().equals(TableColumn.SortType.DESCENDING)) {
        columnLosses.setSortType(TableColumn.SortType.ASCENDING); // needed for correct multicolumn sorting
    }
});
0
On

Unfortunately, the table is not designed for the sorting to be changed in this way. However, I came up with a great solution to this problem after looking at the TableColumnHeader source code.

First you need to reverse the comparator on the column of interest:

Comparator<T> originalComparator = column.getComparator();
column.setComparator((v1, v2) -> originalComparator.compare(v2, v1));

This gets the sorting to be descending on first click and ascending on second click. The only problem is that the arrow in the column header hasn't changed, so will point up when it should point down and down when it should point up. To fix this, I added a style class to the column:

column.getStyleClass().add("descending-first-column");

and added this to my stylesheet:

.descending-first-column.column-header GridPane {
    -fx-rotate: 180;
    -fx-scale-x: -1;
}

.descending-first-column.column-header GridPane .sort-order {
    -fx-scale-y: -1;
}

This works as follows. The rotation 180 degrees ensures the arrow points the right way. But when you sort multiple columns at a time the arrow will also have dots or a number (for columns 4 and above in the sort order). The GridPane contains the dots and the number as well as the arrow, so all of them get rotated 180 degrees. This works perfectly for the dots, but not for the number, which gets switched to the other side and turned upside down. So to put the number back to how it should be, I flipped the GridPane horizontally (-fx-scale-x: -1) and flipped the number vertically (-fx-scale-y: -1). And fortunately that just works without having to fiddle with translations.