Question

I have a simple TableView (Java FX 2.0, but I suppose the problem is fairly generic) that gets the default sorting feature. However the table has a total in its last line so I would like to exclude that last line from the sorting algorithm.

I found a solution for a Swing JTable that consists in creating a separate table for the total row - that would be transposable to a TableView but it seems a bit cumbersome. I have tried implementing my own Comparator but I don't think it is possible to create one that would work in both ascending & descending orders.

Was it helpful?

Solution

With JavaFX 8 it is possible to define a sorting policy, and the problem is easier to fix. Assuming the row containing the totals is TOTAL:

table.sortPolicyProperty().set(t -> {
    Comparator<Row> comparator = (r1, r2) -> 
         r1 == TOTAL ? 1 //TOTAL at the bottom
       : r2 == TOTAL ? -1 //TOTAL at the bottom
       : t.getComparator() == null ? 0 //no column sorted: don't change order
       : t.getComparator().compare(r1, r2); //columns are sorted: sort accordingly
    FXCollections.sort(t.getItems(), comparator);
    return true;
});

OTHER TIPS

Based on lolsvemir's answer I successfully implements a "TOTAL" with a custom Comparator. I set the comparator with column's sortTypeProperty: col.setComparator(new BigDecimalColumnComparator(col.sortTypeProperty()));

Custom comparator:

public class BigDecimalColumnComparator implements Comparator<BigDecimal> {
    private final ObjectProperty<TableColumn.SortType> sortTypeProperty;

    public BigDecimalColumnComparator(ObjectProperty<TableColumn.SortType> sortTypeProperty) {
        this.sortTypeProperty = sortTypeProperty;
    }

    @Override
    public int compare(BigDecimal o1, BigDecimal o2) {
        TableColumn.SortType sortType = sortTypeProperty.get();
        if (sortType == null) {
            return 0;
        }

        if (o1 instanceof TotalBigDecimal) {
            if (sortType == TableColumn.SortType.ASCENDING) {
                return 1;
            } else {
                return -1;
            }
        } else if (o2 instanceof TotalBigDecimal) {
            if (sortType == TableColumn.SortType.ASCENDING) {
                return -1;
            } else {
                return 1;
            }
        }

        return o1.compareTo(o2);
    }
}

And if you always return 0 in your Comparator's compareTo() method? It means that all elements "are the same" and no swapping of places should occur?

I had the same issue and, due to TableView property to call cell factory for as many rows as are visible in the table, and not only for row indexes up to the size of list set by TableView.setItems() method, IMO it is better to set summary value directly by cell factory for every column which needs to have it.

Here is the simple example of such cell factory class:

public class LocalDateTableCell<T> implements 
    Callback<TableColumn<T, LocalDate>, TableCell<T, LocalDate>> {

  @Override
  public TableCell<T, LocalDate> call(TableColumn<T, LocalDate> p) {
    TableCell<T, LocalDate> cell = new TableCell<T, LocalDate>() {
      @Override
      protected void updateItem(LocalDate item, boolean empty) {
        super.updateItem(item, empty);

        if (item == null || empty) {
          setText(null);
        } else {
          setText(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
            .format(item));
        }

        if (getTableView().getItems().size() == getIndex())
          setText("Test"); // This shows in the summary row
      }
    };

    return cell;
  }
}

Advantages of this approach are that such row is not involved in sorting at all (because it is not in the table items list) and not selectable also. Additionally, with overriding TableRow factory is easy to set different background color for whole summary row and to distinguish it from other rows.

However, this additional row will not be visible while scrolling down because TableView scrolling implementation does not know for the additional row and will scroll only to the last row from the items list at the bottom of the viewport. That can be solved by overriding TableViewSkin.getItemCount() method, like in the following example. Overriding TableView itself is not necessary, but is recommended if such table is used often.

public class MyTableViewSkin<T> extends TableViewSkin<T> {
  private TableView<T> tableView;

  public MyTableViewSkin(final TableView<T> tableView) {
    super(tableView);

    this.tableView = tableView;
  }

  @Override
  public int getItemCount() {
    return tableView.getItems() == null || tableView.getItems().size() == 0 ? 
      0 : tableView.getItems().size() + 1;
  }
}

public class MyTableView<S> extends TableView<S> {
  public MyTableView() {
    super();

    setSkin(new MyTableViewSkin<>(this));
  }
}

This may look like a hack, but works like a charm.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top