在 Java Swing(使用 swingx)中如何按照与另一个 table 相同的顺序对一个 table 中的行进行排序

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

我有一个 table,行数为 x,我有第二个 table,行数相同但列和元数据不同,它们有不同的 table 模型。但每一行代表相同的对象(一首歌)。

我想同步两个 table 之间的行排序,例如,如果我对 table 1 的第 2 列进行排序,那么 table 的行将在同样的顺序。但目前,我只是通过匹配排序键进行排序,所以在同一列上排序(但因为不同的数据得到不同的结果)

例如

起点

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

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

如果我按 table 1 排序,第 2 列升序我想得到

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

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

但我明白了

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

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

我的排序是通过调用 table 2 上的 setSortKeys() 到 table 1 的 getSortKeys() 来完成的,反之亦然。我明白为什么它不起作用,我告诉 table 2 按与 table 1 相同的第 2 列升序排序,但当然这些列在每个 table 中都有不同的数据。但是我无法找到一种方法让 table 2 排序为 table 1 的最终顺序。

一个解决方案是让两个 table 共享相同的 table 模型并只显示与其 table 相关的列,但这需要对代码,所以我希望有一个更具体的解决方案来解决排序问题。

我正在使用 Java 11 和 swingx 最新版本 1.6.4(我知道很旧)但是这代表排序到标准 Java(我之前使用的早期版本有自己的排序)所以不是真正的 swingx 问题。

在我的应用程序中,真实世界的情况如下,每一行代表一首歌曲,标签显示该歌曲的元数据。编辑菜单下的选项卡都共享相同的模型,并且都使用上述 setSortKeys() 方法工作。所以我在这里对情绪攻击

进行了排序

编辑元数据选项卡

如果我转到另一个选项卡,我们会看到行按相同顺序排序

另一个编辑元数据选项卡,排序相同

但如果我转到“编辑 ID3”选项卡,我们会看到行已按不同顺序排序。

ID3 编辑选项卡排序不同

这是因为 ID3 编辑选项卡以不同的格式 (ID3) 显示元数据并且具有不同的 table 模型,因此模型中表示的列 x 存储不同的数据。

请注意,因为所有模型都将 rowno 存储在第一列中,所以对我的 rowno 列进行排序适用于所有选项卡。

所以从用户的角度来看,他们只是在查看相同 table 的不同选项卡,因此希望选项卡的排序保持一致

这是我在评论中的意思:

import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableRowSorter;
import java.awt.*;

public class TablesExample extends JPanel {
    static class MyTableModel extends AbstractTableModel {
        private String[] columnNames = {"Row Id",
                "Person",
                "Fruit"};
        private Object[][] data = {
                {"1", "Tom", "Orange"},
                {"2", "Jane", "Apple"},
                {"3", "Fred", "Pear"},
                {"4", "Steve", "Lemon"},
                {"5", "Jim", "Plum"}
        };

        public int getColumnCount() {
            return columnNames.length;
        }

        public int getRowCount() {
            return data.length;
        }

        public String getColumnName(int col) {
            return columnNames[col];
        }

        public Object getValueAt(int row, int col) {
            return data[row][col];
        }
    }

    public static void main(String[] args) {
        JFrame frame = new JFrame("Tables Example");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JTabbedPane tabbedPane = new JTabbedPane();
        tabbedPane.setPreferredSize(new Dimension(500, 100));

        TablesExample newContentPane = new TablesExample();
        newContentPane.setOpaque(true);

        MyTableModel model = new MyTableModel();
        TableRowSorter<MyTableModel> sorter = new TableRowSorter<>(model);
        JTable table = new JTable(model);
        table.setRowSorter(sorter);
        TableColumn column2 = table.getColumnModel().getColumn(2);
        column2.setMinWidth(0);
        column2.setMaxWidth(0);
        column2.setWidth(0);

        JScrollPane scrollPane = new JScrollPane();
        scrollPane.setViewportView(table);
        tabbedPane.add("Persons", scrollPane);

        JTable table2 = new JTable(model);
        table2.setRowSorter(sorter);
        TableColumn column1 = table2.getColumnModel().getColumn(1);
        column1.setMinWidth(0);
        column1.setMaxWidth(0);
        column1.setWidth(0);

        JScrollPane scrollPane2 = new JScrollPane();
        scrollPane2.setViewportView(table2);
        tabbedPane.add("Fruits", scrollPane2);

        frame.setContentPane(tabbedPane);

        frame.pack();
        frame.setVisible(true);
    }
}

制作原型。

所以使用 swingx 我们实现了 TableSortController 的子类并覆盖 toggleSortOrder() 并将其设置为主 table[ 的 rowSorter =16=]

        public void toggleSortOrder(int column)
        {
            .........
            setSortKeys(newKeys);
        
            for(int i=0; i < getModelWrapper().getRowCount(); i++)
            {
                SecondTable.instanceOf().getRealModel()
                    .setValueAt(convertRowIndexToView(i), i, ID3TagNames.INDEX_SYNC_SORT);
            }
        
            newKeys = new ArrayList<>();
            SecondTable.instanceOf().getTable().getRowSorter().setSortKeys(newKeys);
        
            newKeys.add(new SortKey(ID3TagNames.INDEX_SYNC_SORT, this.getFirstInCycle()));
            SecondTable.instanceOf().getTable().getRowSorter().setSortKeys(newKeys);

        }

逻辑是在主 table 上进行正常排序,然后在第二个 table 上设置隐藏列以存储每一行​​的视图索引。然后删除第二个 table 上的任何现有排序,然后按隐藏列排序。

请注意,需要两次调用 setSortKey(),因为如果您在主 table 上按一列排序,然后在这两种情况下进行另一种排序,将按 [= 对第二个 table 进行排序28=] 升序,因此超类中的以下代码 DefaultRowSorter.setSortKeys() 将阻止完成排序,因为排序键将与之前的排序相同

        if (!this.sortKeys.equals(old)) {
            fireSortOrderChanged();
            if (viewToModel == null) {
                // Currently unsorted, use sort so that internal fields
                // are correctly set.
                sort();
            } else {
                sortExistingData();
            }
        }

现在,在这个原型中,我们在 SecondTable 上有一个默认的排序控制器,因为我们不希望它也进行特殊处理。但可能想要对两者进行排序,因此需要 toggleSort() 代码来检查 table 它们链接到什么并采取相应的行动。

我想到了以下方法,它使用第一个 table 的 rowSorterrowIndex 翻译为第二个 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);

第一个 table、TableOneModel 的模型是 AbstractTableModel 的子类,实现了所需的方法:

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];
    }
}

第二个 table、TableTwoModel 的模型存储对第一个 table 的 rowSorter 的引用以进行行索引的转换:

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();
    }
}

当第一个 table 的排序顺序发生变化时,第二个 table 模型调用 fireTableDataChanged() 通知视图它需要更新所有数据。

编辑: 正如 Paul 在评论中提到的,第二个 table 的排序顺序也应更改第一个 table。所以同步应该是双向的。

在更新版本中,两个 table 都使用 TableTwoModel 第一个 table 将自己标识为 leading一个。 (正如我一直在编写更新,我意识到这不是必需的。)因此,TableTwoModel 的实现基本上保持不变。我将 TableTwoModel 中的 sorterChanged 更改为仅针对 SORTED 事件类型调用 fireTableDataChanged(),即 table 排序完成时。稍微优化一下。

棘手的部分是 table 的 sync/reset RowSorter。然而,事实证明解决方案非常简单。这是通过为每个行排序器安装 RowSorterListener 来实现的。如果事件类型是SORT_ORDER_CHANGED并且这个RowSorter的sort keys列表是non-empty,other的sort keys设置为null .因此,只有一个 table 被排序,另一个遵循已排序的排序顺序。