Search code examples
javaswingjtableswingx

In Java Swing (using swingx) how to sort rows in one table in same order as another table


I have a table with x num of rows, I have a second table with the same number of rows but different columns and metadata, they have different table models. but each row represents the same object (a song).

I want to synchronize row sorting between the two tables so for example if I sort on column 2 of table 1 then rows of the table will be sorted in the same order. But currently, I just have sorted by matching sort keys so sort on the same column (but because different data get different results)

e.g

Starting point

Table 1
1    tom    
2    jane
3    fred
4    steve
5    jim

Table 2
1    oranges
2    apples
3    pears
4    lemons
5    plums

If I sort by table 1, column 2 Ascending I want to get

Table 1
2    jane
5    jim
3    fred
4    steve
1    tom

Table 2
2    apples
5    plums
3    pears
4    lemons
1    oranges

but I get

Table 1
2    jane
5    jim
3    fred
4    steve
1    tom

Table 2
2    apples
4    lemons
1    oranges
3    pears
5    plums

My sorting is done by calling setSortKeys() on table 2 to the getSortKeys() of table 1 and vice versa. I can see why it doesn't work, I am telling table 2 to sort by column 2 ascending the same as table 1 but of course these columns have different data in each table. But I can't work out a way to get table 2 to sort to the final order of table 1 instead.

One solution would be for both tables to share the same table model and just show the columns relevant to their table, but that would require a major rework of the code, so I am hoping for a more specific solution just to resolve the sorting issue.

I am using Java 11, and swingx latest version 1.6.4 (i know very old) but this delegates sorting to standard Java (earlier version that I was previously using had its own sorting) so not really a swingx question.

The real world situation, within my application is as follows, each row represents a song, and the tabs show metadata for that song. the tabs under the edit menu all share same model and all work using the setSortKeys() method described above. So here i have sorted on Mood Aggressive column

Edit metadata tab

and if I go to another tab, we see the rows are sorted in same order

Another Edit metadata tab, sorted same order

but if I go to the Edit ID3 tab, we see the rows have been sorted in different order.

ID3 Edit tab sorted different order

This is because ID3 Edit tab shows the metadata in different format (ID3) and has different table model so column x represent in the model stores different data.

Note because all models store the rowno in first column, sorting my the rowno column works for all tabs.

So from a user point of view they are just viewing different tabs of the same table, and therefore would expect sort to be consistent for the tabs


Solution

  • I came up with the following approach which translates rowIndex for the second table using rowSorter of the first table.

        TableOneModel tableOneData = new TableOneModel( /* omitted */ );
        JTable tableOne = new JTable(tableOneData);
        TableRowSorter<TableOneModel> sorterOne = new TableRowSorter<>(tableOneData);
        tableOne.setRowSorter(sorterOne);
    
        TableTwoModel tableTwoData = new TableTwoModel(
                /* omitted */,
                sorterOne);
        JTable tableTwo = new JTable(tableTwoData);
    

    The model for the first table, TableOneModel, is a subclass of AbstractTableModel implementing the required methods:

    private static class TableOneModel extends AbstractTableModel {
        private final String[] columnNames;
        private final Object[][] data;
    
        public TableOneModel(String[] columnNames, Object[][] data) {
            this.columnNames = columnNames;
            this.data = data;
        }
    
        public int getRowCount() { return data.length; }
    
        public int getColumnCount() { return columnNames.length; }
    
        public Object getValueAt(int rowIndex, int columnIndex) {
            return data[rowIndex][columnIndex];
        }
    }
    

    The model for second table, TableTwoModel, stores the reference to the rowSorter of the first table to do the translation of row indexes:

    private static class TableTwoModel extends TableOneModel
            implements RowSorterListener {
    
        private final RowSorter<? extends TableModel> otherSorter;
    
        public TableTwoModel(String[] columnNames, Object[][] data,
                             RowSorter<? extends TableModel> sorter) {
            super(columnNames, data);
            this.otherSorter = sorter;
            installListeners();
        }
    
        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            return super.getValueAt(
                    otherSorter.convertRowIndexToModel(rowIndex),
                    columnIndex);
        }
    
        private void installListeners() {
            otherSorter.addRowSorterListener(this);
        }
    
        @Override
        public void sorterChanged(RowSorterEvent e) {
            fireTableDataChanged();
        }
    }
    

    When the sorting order of the first table changes, the second table model calls fireTableDataChanged() to notify the view it needs to update all the data.

    Edit: As Paul mentioned in the comment, the sort order of the second table should also change the first table. So the sync should work both ways.

    In the updated version, both tables use TableTwoModel and the first table identifies itself as the leading one. (Just as I've been writing the update, I realised this wasn't necessary.) Thus, the implementation of TableTwoModel remains basically unchanged. I changed sorterChanged in TableTwoModel to call fireTableDataChanged() only for SORTED event type that is when the sorting of the table is complete. It's a little optimisation.

    The tricky part was to sync/reset RowSorter of the tables. However, the solution proved to be pretty simple. This is achieved by installing RowSorterListener to each row sorter. If the event type is SORT_ORDER_CHANGED and the list of sort keys of this RowSorter is non-empty, the sort keys of the other are set to null. Thus, only one table is sorted and the other follows the sort order of the sorted one.