15

I have a JTable that uses a DefaultTableModel and I allow for sorting when the user clicks on the column headers. However, when the user clicks on a header for a column that has data of type integer it does not sort properly. It seems like it is sorting by String instead of an integer type.

Here is the part of my code where I actually add the data to the table:

        DefaultTableModel aModel = (DefaultTableModel) mainView.logEntryTable.getModel();
                    ResultSetMetaData rsmd;             try {
            mainView.logEntriesTableModel.setRowCount(0);
            rsmd = rs.getMetaData();

            int colNo = rsmd.getColumnCount();
            while(rs.next()){
                Object[] objects = new Object[colNo];
                for(int i=0;i<colNo;i++){
                    objects[i]=rs.getObject(i+1);
                }
                aModel.addRow(objects);
                count++;
            }
            mainView.logEntryTable.setModel(aModel);
            mainView.logEntryTable.getColumnModel().getColumn(0).setMaxWidth(80);

So I tried to override that method and ended up with this:

            @Override
            public Class<?> getColumnClass(int columnIndex){
                if( columnIndex == 0){
                    // Return the column class for the integer column
                }else{
                    // Return the column class like we normally would have if we didn't override this method
                }

                return null;
            }
        };

I've never overridden this before and I'm not quite sure what it is expecting me to do here.

Brian T Hannan
  • 3,925
  • 18
  • 56
  • 96
  • 2
    overriding getColumnClass is recommended nearly always: not only is the sorting (of comparables) done as expected, but typically the formatted rendering and type conversion when editing is handled as well. BTW: never return null from the getColumnClass ... – kleopatra Jul 06 '11 at 08:13

5 Answers5

21

Try this little example.

Sorted 1st column

The better way

As suggested by Kleopatra, defining a column class relevant for each, will be enough to get the data to be sorted correctly.

import javax.swing.*;
import javax.swing.table.*;
import java.util.Comparator;

class TableSorting {
    public static void main(String[] args) {
        Object[][] data = {
            {new Integer(1), "Don't Let Go", new Integer(179)},
            {new Integer(2), "Photograph", new Integer(29)},
            {new Integer(3), "Hash Pipe", new Integer(186)},
            {new Integer(4), "Island In The Sun", new Integer(200)},
            {new Integer(5), "Crab", new Integer(154)},
            {new Integer(6), "Knock-Down Drag-Out", new Integer(128)},
            {new Integer(7), "Smile", new Integer(158)},
            {new Integer(8), "Simple Pages", new Integer(176)},
            {new Integer(9), "Glorious Day", new Integer(160)},
            {new Integer(10), "O Girlfriend", new Integer(230)}
        };
        Object[] columns = {"Track #", "Title", "Length"};
        DefaultTableModel model = new DefaultTableModel(data,columns) {
            @Override
            public Class getColumnClass(int column) {
                switch (column) {
                    case 0:
                        return Integer.class;
                    case 1:
                        return String.class;
                    case 2:
                        return Integer.class;
                    default:
                        return String.class;
                }
            }
        };
        JTable table = new JTable(model);
        JScrollPane scroll = new JScrollPane(table);
        table.setAutoCreateRowSorter(true);
        JOptionPane.showMessageDialog(null, scroll);
    }
}

Original, using a comparator

import javax.swing.*;
import javax.swing.table.*;
import java.util.Comparator;

class TableSorting {
    public static void main(String[] args) {
        Object[][] data = {
            {new Integer(1), "Don't Let Go", new Integer(179)},
            {new Integer(2), "Photograph", new Integer(29)},
            {new Integer(3), "Hash Pipe", new Integer(186)},
            {new Integer(4), "Island In The Sun", new Integer(200)},
            {new Integer(5), "Crab", new Integer(154)},
            {new Integer(6), "Knock-Down Drag-Out", new Integer(128)},
            {new Integer(7), "Smile", new Integer(158)},
            {new Integer(8), "Simple Pages", new Integer(176)},
            {new Integer(9), "Glorious Day", new Integer(160)},
            {new Integer(10), "O Girlfriend", new Integer(230)}
        };
        Object[] columns = {"Track #", "Title", "Length"};
        JTable table = new JTable(data, columns);
        JScrollPane scroll = new JScrollPane(table);
        DefaultTableModel model = new DefaultTableModel(data,columns);
        TableRowSorter trs = new TableRowSorter(model);

        class IntComparator implements Comparator {
            public int compare(Object o1, Object o2) {
                Integer int1 = (Integer)o1;
                Integer int2 = (Integer)o2;
                return int1.compareTo(int2);
            }

            public boolean equals(Object o2) {
                return this.equals(o2);
            }
        }

        trs.setComparator(0, new IntComparator());

        table.setRowSorter(trs);

        scroll = new JScrollPane(table);
        table.setAutoCreateRowSorter(false);
        JOptionPane.showMessageDialog(null, scroll);
    }
}
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
  • nice to use an optionPane as top-level container in an example :-) – kleopatra Jul 06 '11 at 08:15
  • Yes, but it's probably even nicer to wrap GUI calls in a Runnable and invoke it later. I'm not sure if my defense is 'batteries not included' or '(shrugs) I was just too lazy'. ;) – Andrew Thompson Jul 06 '11 at 08:28
  • have seen it - could refrain from mentioning it, with effort, hehe – kleopatra Jul 06 '11 at 08:42
  • Recently, almost every answer I'm looking for is answered elegantly by you :) – Maroun May 20 '13 at 08:26
  • 1
    @MarounMaroun I think it is the screenshot that draws the eye (& occasional up-vote). Kind of 'Been there, done that SSCCE, got the screenshot'. ;) – Andrew Thompson May 20 '13 at 10:22
  • was so blinded by the niceness that I overlooked the suboptimal content which [got copied recently](http://stackoverflow.com/a/16760410/203657): there is much reason to install a custom comparator on a column containing a comparable type. Simply let the model return that type and be happy. -1 for equalily with the copier ;-) – kleopatra Jun 24 '13 at 08:29
  • @kleopatra I just tried what (I think) you recommended to see a sort order of "1,10,2,3,4.." in the 1st column. If I ask a question on it, will you reply? – Andrew Thompson Jun 24 '13 at 08:57
  • me recommeding such ... vote me down, was brain-dumb then! Yeah, sure - but maybe my answer to the copier already covers your question? – kleopatra Jun 24 '13 at 09:27
9

Well, the docs for DefaultTableModel do state:

Warning: DefaultTableModel returns a column class of Object. When DefaultTableModel is used with a TableRowSorter this will result in extensive use of toString, which for non-String data types is expensive. If you use DefaultTableModel with a TableRowSorter you are strongly encouraged to override getColumnClass to return the appropriate type.

So it sounds like it's just going to be converting values to strings, which would be consistent with what you're seeing.

Have you tried either overriding getColumnClass() or calling setComparator() for the appropriate TableRowSorter?

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
3

Here is the answer: Problems with JTable sorting of integer values

The idea is to define classes for columns.

myTable.setModel(new DefaultTableModel(Object[][] tableData, String[] columnsNames){
    Class[] types = { Boolean.class, Boolean.class, String.class, String.class };

    @Override
    public Class getColumnClass(int columnIndex) {
        return this.types[columnIndex];
    }
});
Community
  • 1
  • 1
Zakhar
  • 605
  • 8
  • 17
0

DefaultTableModel returns a column class of Object. As such all comparisons will be done using toString. This may be unnecessarily expensive. If the column only contains one type of value, such as an Integer, you should override getColumnClass and return the appropriate Class. This will dramatically increase the performance of this class.

In this case you should add the snippet code below in your table model:

    @Override
public Class<?> getColumnClass(int columnIndex) {
    if (db.isEmpty()) {
        return Object.class;
    }
    return getValueAt(0, columnIndex).getClass();
}
sonvt8
  • 1
  • 1
  • 1
-1

Most straightforward way I found is to just set the comparator for the specific column. Use it when you already have a TableRowSorter (first line) and set the table to use it (last line).

    TableRowSorter<TableModel> rowSorter = new TableRowSorter<>(tablePanel.getTable().getModel());
    rowSorter.setComparator(1,
            new Comparator<String>() {
                @Override
                public int compare(String o1, String o2) {
                    return Integer.valueOf(o1).compareTo(Integer.valueOf(o2));
                }
            });
    tablePanel.getTable().setRowSorter(rowSorter);