1

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 empty row i am adding automtacly a new empty row below it. (It suppose to act like the Microsoft visual tables)

I am using the java default row sorter,

The problem is that i need the empty row to be the last row all the time! but after sorting the table it become the first row.

The "compare(int model1, int model2)" method of the DefaultRowSorter class is getting 2 row numbers and return -1 if the value of the first row is null and 1 if the value of the second row is null. and incase of DESCENDING it mult by -1 to get the inverted order.

            //Treat nulls as < then non-null
            if (v1 == null) {
                if (v2 == null) {
                    result = 0;
                } else {
                    result = -1;
                }
            } else if (v2 == null) {
                result = 1;
            } else {
                result = sortComparators[counter].compare(v1, v2);
            }
            if (sortOrder == SortOrder.DESCENDING) {
                result *= -1;
            }

The empty line is sorted as the smallest value and incase of DESCENDING it will be the first line (because of the mult by -1) and causes alot of problems.

I could overide it and incase of the empty row(usualy the last row) do not mult by -1 in DESCENDING mode, and it will be the last row after any sorting. But the problem is that the "compare" method and it's caller "Row" inner class are private inside the DefaultRowSorter.

Is there a way to avoid sorting the empty row and make it always the last row?

kleopatra
  • 51,061
  • 28
  • 99
  • 211
Billbo bug
  • 358
  • 2
  • 5
  • 8

5 Answers5

5

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);
        }
    }
}
Peter Lang
  • 54,264
  • 27
  • 148
  • 161
2

The DefaultRowSorter is definitely flawed.

The only option is to create your own RowSorter based on the DefaultRowsorter and correct the problem.

Hans Olsson
  • 54,199
  • 15
  • 94
  • 116
Fedearne
  • 7,049
  • 4
  • 27
  • 31
  • This solution is well known, but i want to know if there is a better solution that not includes copy an rewrite the DefaultRowSorter. – Billbo bug Feb 23 '10 at 09:08
  • I agree that is an annoying solution. – Fedearne Feb 23 '10 at 10:27
  • 1
    Those links are broken, here are some possible hits at OTN. 1) [Sort JTable with an empty Row](http://forums.oracle.com/forums/thread.jspa?threadID=1349003), 2) [TableRowSorter on TableModel that allows adding/removing table rows](http://forums.oracle.com/forums/message.jspa?messageID=9337963), 3) [Best method to leave a specified row of a JTable unsorted?](http://forums.oracle.com/forums/message.jspa?messageID=9272096) or 4) [Adding new row to the JTable at the end of the row](http://forums.oracle.com/forums/message.jspa?messageID=5695921). – Andrew Thompson May 31 '11 at 03:58
1

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);
Christophe Weis
  • 2,518
  • 4
  • 28
  • 32
0

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

droperto
  • 117
  • 10
-2

Set a different comparator using DefaultRowSorter's setComparator method.

Community
  • 1
  • 1
Suraj Chandran
  • 24,433
  • 12
  • 63
  • 94
  • 1
    The handling of null values is done before invoking the custom comparator. (as the question also states) – Fedearne Feb 23 '10 at 08:48