1

Does someone know a good way to display the sorting icons in the header of a JTable, without using the build in sort functionality?

The sorting is done by the table model (actually a database) and not by the JTable itself. Thats why the automatic display of the icons doesn't work. Maybe one can insert a dummy RowSorter that does nothing, but makes the sort icons appear?

I found a better Solution

I just wrote my own RowSorter, so that the sorting does not have any effect, but redirects the sorting request to the model instead. That way the sort order is displayed by the look and feel itself. Some Pseudocode:

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import javax.swing.RowSorter;
import xyz.SortableTableModel;

public class MyRowSorter<M extends SortableTableModel> extends RowSorter<M> {

    private M tableModel;
    private List<? extends SortKey> sortKeys = new LinkedList<>();

    public MyRowSorter(M tableModel) {
        this.tableModel = tableModel;
    }

    @Override
    public M getModel() {
        return tableModel;
    }

    @Override
    public void toggleSortOrder(int column) {

        // redirecting sort request to model and modification of sortKeys

        List<? extends SortKey> newSortKeys = ...;
        setSortKeys(newSortKeys);
    }

    @Override
    public int convertRowIndexToModel(int index) {
        return index; // will always be the same
    }

    @Override
    public int convertRowIndexToView(int index) {
        return index; // will always be the same
    }

    @Override
    public void setSortKeys(List<? extends SortKey> keys) {
        if (keys == null) {
            sortKeys = Collections.EMPTY_LIST;
        } else {
            sortKeys = Collections.unmodifiableList(keys);
        }
        fireSortOrderChanged();
    }

    @Override
    public List<? extends SortKey> getSortKeys() {
        return sortKeys;
    }

    @Override
    public int getViewRowCount() {
        return tableModel.getRowCount();
    }

    @Override
    public int getModelRowCount() {
        return tableModel.getRowCount();
    }

    // no need for any implementation
    @Override public void modelStructureChanged() { }
    @Override public void allRowsChanged() { }
    @Override public void rowsInserted(int firstRow, int endRow) { }
    @Override public void rowsDeleted(int firstRow, int endRow) { }
    @Override public void rowsUpdated(int firstRow, int endRow) { }
    @Override public void rowsUpdated(int firstRow, int endRow, int column) { }

}
user3190008
  • 23
  • 1
  • 5
  • You can try to write a custom TableHeader Renderer with your icon behavior. – alex2410 Jan 13 '14 at 11:46
  • I tried that already. But the problem with this solution is, that the header renderer should use the system look and feel. A custom renderer obviously doesn't use the look and feel at all (except for looking like a lable or something). – user3190008 Jan 13 '14 at 11:58
  • See my answer that provides solution with default L&F and decoration for header. – alex2410 Jan 13 '14 at 12:00
  • A custom renderer obviously doesn't use the look and feel at all - why do you think that there isn't applied L&F – mKorbel Jan 13 '14 at 12:44

3 Answers3

1

In that case you can try to write a custom TableCellRenderer for JTableHeader.

Here is simple example of renderer:

private static class MyRenderer implements TableCellRenderer {

    private ImageIcon icon1;
    private ImageIcon icon2;
    private TableCellRenderer defaultRenderer;

    MyRenderer(JTable t){
        defaultRenderer = t.getTableHeader().getDefaultRenderer();
        icon1 = new ImageIcon(getClass().getResource("1.png"));
        icon2 = new ImageIcon(getClass().getResource("2.png"));
    }

    @Override
    public Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
        Component c = defaultRenderer.getTableCellRendererComponent(  table, value, isSelected, hasFocus, row, col);
        if(col%2 == 0){
            ((JLabel)c).setIcon(icon1);
        } else {
            ((JLabel)c).setIcon(icon2);
        }
        return c;
    }
}

Here icon1 and icon2 is your sorting icons.

And you can set that renderer for your JTableHeader like next:

table.getTableHeader().setDefaultRenderer(new MyRenderer(table));

table - is your JTable.

alex2410
  • 10,904
  • 3
  • 25
  • 41
  • This is a very simple solution, but it has it's drawbacks: 1. You can't use a custom icon for the column anymore. It is occupied by (used for) the sorting icon. 2. There is a visual difference between the standard sort mechanism and the custom one you just wrote. See the image below: – user3190008 Jan 13 '14 at 13:03
  • ![Image of differences](http://www.pasteall.org/pic/show.php?id=65231). Default behavior is at the bottom, your custom solution at the top. – user3190008 Jan 13 '14 at 13:10
  • I suppose, you can try to manage position of icon by yourself with help of more complex Renderer. – alex2410 Jan 13 '14 at 13:14
  • That does not work, because i need to pass the Component object from the original renderer, which can't be expanded. If i don't, then the look and feel (for example animations) is sadly broken. So i wonder if there isn't the possibility to add a fake/dummy RowSorter or something like this, which simply obmits any sorting, but sets the right state for the look and feel, so that i don't have to implement a faulty custom renderer. – user3190008 Jan 13 '14 at 13:30
1

The sorting is done by the table model (actually a database) and not by the JTable itself.

Check out the DefaultRowSorter class. Maybe you use the setSortsOnUpdates(...) and setSortKeys(...) so the sorting icons match the sort from the database. You could try:

  1. Creating an empty model
  2. Set the sort keys
  3. use setSortsOnUpdates(false);
  4. Update the model using the setDataVector() (or some equivalent method if using a custom model)

Note this approach assumes you have created the TableModel with column names and no data and added the model to the JTable. I think you will then also need to use:

table.setAutoCreateColumnsFromModel(false);

to prevent the TableColumnModel from being recreated when you load the data into the model.

camickr
  • 321,443
  • 19
  • 166
  • 288
  • Actually i just found a very similar solution and posted it directly under my question. I just implemented my own RowSorter, that does more or less nothing, except storing the SortKey's and redirecting the sorting requests to the TableModel instead, which does the actual sorting. – user3190008 Jan 13 '14 at 17:23
0

Solution is tricky when you want your code to work with other existing Swing layouts (I am talking about com.formdev .... flatlaf ). These L&Fs create a special Header renderer.

Here is a simple solution that will work with all main L&Fs on the market (tatoo, formdev, jgoodies). The trick is to subclass from DefaultTableCellHeaderRenderer but also to pass the table look and feel current header renderer as parameter.

        // this custom renderer will display the sorting icon for all afftected columns.
        class CustomTableHeaderRenderer extends DefaultTableCellHeaderRenderer  implements TableCellRenderer{
            final private Icon ascIcon   = UIManager.getIcon("Table.ascendingSortIcon");
            final private Icon descIcon = UIManager.getIcon("Table.descendingSortIcon");
            TableCellRenderer iTableCellRenderer = null;
            
            public CustomTableHeaderRenderer(TableCellRenderer tableCellRenderer)
            {
                iTableCellRenderer = tableCellRenderer;
            }           
            
            
            public Component getTableCellRendererComponent(JTable table,  Object value, boolean isSelected, boolean hasFocus, int row,  int column) {
                JLabel label = (JLabel) iTableCellRenderer.getTableCellRendererComponent( table,   value,  isSelected, hasFocus,  row,   column) ;
                List<? extends SortKey> sortKeys = table.getRowSorter().getSortKeys();
                label.setIcon(null);
                for (SortKey sortKey : sortKeys) {
                    if (sortKey.getColumn() == table.convertColumnIndexToModel(column)){
                        SortOrder o = sortKey.getSortOrder();
                        label.setIcon(o == SortOrder.ASCENDING ? ascIcon : descIcon);
                        break;
                    }
                }
                return label;
            }
        }

        yourTable.getTableHeader().setDefaultRenderer( new CustomTableHeaderRenderer(  yourTable.getTableHeader().getDefaultRenderer()      ));
Adrian Faur
  • 61
  • 1
  • 2