I am using JTable with an empty row at the bottom of the table in order to give the ability of adding new line to the table.
After insert or writing data in the empt
I know it is an old thread, but I was stuck here trying to find a way. Please, let me know if there is a new way not mentioned before. Actually this is my first post on SO, but since I spent lots of time finding a solution for that I decided to share.
I solved that ordering problem by, instead of using 'null' as my 'new entry', I created a specific object (in my case from Object class itself, but it could be something more meaninful like a 'NewEntryDoNotCompareWithMePleaseLetMeBeTheLastEntryIBegYouDoNotSortMe' empty class).
When comparing, I first check if the 'object' that came to compare is a object. Not 'instanceof' of course, but (obj.getClass() == Object.class). -- If thats the case, check the sorting order and return 1 or -1.
Feel free to comment the any problems you find with that, I can try to create a small-working-version if someone needs the code.
Cheers, Daniel
The DefaultRowSorter is definitely flawed.
The only option is to create your own RowSorter based on the DefaultRowsorter and correct the problem.
I found another solution by subclassing only TableRowSorter.
From the DefaultRowSorter documentation we know :
The Comparator is never passed null
When subclassing DefaultRowSorter.ModelWrapper, we can return a special Null-Object and create a custom comparator handling that value.
Here follows my code. Probably it is not as efficient as a custom RowSorter implementation and it may still contain some bugs, I did not test everything, but for my requirements it works.
class EmptyTableRowSorter<M extends AbstracTableModel> extends TableRowSorter<M> {
private static final EmptyValue emptyValue = new EmptyValue();
public EmptyTableRowSorter(M model) {
super(model);
}
@Override
public void modelStructureChanged() {
// deletes comparators, so we must set again
super.modelStructureChanged();
M model = getModelWrapper().getModel();
for (int i = 0; i < model.getColumnCount(); i++) {
Comparator<?> comparator = this.getComparator(i);
if (comparator != null) {
Comparator wrapper = new EmptyValueComparator(comparator, this, i);
this.setComparator(i, wrapper);
}
}
}
@Override
public void setModel(M model) {
// also calls setModelWrapper method
super.setModel(model);
ModelWrapper<M, Integer> modelWrapper = getModelWrapper();
EmptyTableModelWrapper emptyTableModelWrapper = new EmptyTableModelWrapper(modelWrapper);
// calls modelStructureChanged method
setModelWrapper(emptyTableModelWrapper);
}
/**
* The DefaulRowSorter implementation does not pass null values from the table
* to the comparator.
* This implementation is a wrapper around the default ModelWrapper,
* returning a non null object for our empty row that our comparator can handle.
*/
private class EmptyTableModelWrapper extends DefaultRowSorter.ModelWrapper {
private final DefaultRowSorter.ModelWrapper modelWrapperImplementation;
public EmptyTableModelWrapper(ModelWrapper modelWrapperImplementation) {
this.modelWrapperImplementation = modelWrapperImplementation;
}
@Override
public Object getModel() {
return modelWrapperImplementation.getModel();
}
@Override
public int getColumnCount() {
return modelWrapperImplementation.getColumnCount();
}
@Override
public int getRowCount() {
return modelWrapperImplementation.getRowCount();
}
@Override
public Object getValueAt(int row, int column) {
M model = EmptyTableRowSorter.this.getModel();
// my model has the empty row always at the end,
// change this depending on your needs
int lastRow = model.getRowCount() - 1;
if (row == lastRow) {
return emptyValue;
}
return modelWrapperImplementation.getValueAt(row, column);
}
//@Override
//public String getStringValueAt(int row, int column) {
// // also override this if there is no comparator definied for a column
//}
@Override
public Object getIdentifier(int row) {
return modelWrapperImplementation.getIdentifier(row);
}
}
/**
* This is a wrapper around another comparator.
* We handle our empty value and if none, we invoke the base comparator.
*/
private class EmptyValueComparator implements Comparator {
private final Comparator defaultComparator;
private final TableRowSorter tableRowSorter;
private final int columnIndex;
public EmptyValueComparator(Comparator defaultComparator, TableRowSorter tableRowSorter, int columnIndex) {
this.defaultComparator = defaultComparator;
this.tableRowSorter = tableRowSorter;
this.columnIndex = columnIndex;
}
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof EmptyValue && o2 instanceof EmptyValue) {
return 0;
}
if (o1 instanceof EmptyValue) {
return adjustSortOrder(1);
}
if (o2 instanceof EmptyValue) {
return adjustSortOrder(-1);
}
return defaultComparator.compare(o1, o2);
}
/**
* Changes the result so that the empty row is always at the end,
* regardless of the sort order.
*/
private int adjustSortOrder(int result) {
List sortKeys = tableRowSorter.getSortKeys();
for (Object sortKeyObject : sortKeys) {
SortKey sortKey = (SortKey) sortKeyObject;
if (sortKey.getColumn() == columnIndex) {
SortOrder sortOrder = sortKey.getSortOrder();
if (sortOrder == SortOrder.DESCENDING) {
result *= -1;
}
return result;
}
}
return result;
}
}
private static class EmptyValue {}
}
Now you can enable sorting in your table.
JTable table = ...;
TableRowSorter tableRowSorter = new EmptyTableRowSorter(table.getModel());
table.setRowSorter(tableRowSorter);
Set a different comparator using DefaultRowSorter's setComparator method.
I gave this a try, and I think I found a solution.
Instead of creating that empty row in the TableModel
, I fake it in the JTable
, and only create it when the user actually enters some data.
The RowSorter
only sorts rows of the TableModel
, so our row is not affected and remains as the last row.
public class NewLineTable extends JTable {
@Override
public int getRowCount() {
// fake an additional row
return super.getRowCount() + 1;
}
@Override
public Object getValueAt(int row, int column) {
if(row < super.getRowCount()) {
return super.getValueAt(row, column);
}
return ""; // value to display in new line
}
@Override
public int convertRowIndexToModel(int viewRowIndex) {
if(viewRowIndex < super.getRowCount()) {
return super.convertRowIndexToModel(viewRowIndex);
}
return super.getRowCount(); // can't convert our faked row
}
@Override
public void setValueAt(Object aValue, int row, int column) {
if(row < super.getRowCount()) {
super.setValueAt(aValue, row, column);
}
else {
Object[] rowData = new Object[getColumnCount()];
Arrays.fill(rowData, "");
rowData[convertColumnIndexToModel(column)] = aValue;
// That's where we insert the new row.
// Change this to work with your model.
((DefaultTableModel)getModel()).addRow(rowData);
}
}
}