简体   繁体   English

如何使用setAutoCreateColumnsFromModel(false)从TableModel和TableColumnModel中删除JTable列?

[英]How to remove a JTable column from both TableModel and TableColumnModel with setAutoCreateColumnsFromModel(false)?

See at the end my SSCCE code. 最后看我的SSCCE代码。 What I'm trying to achieve is: 我想要实现的是:

  • Equal number of columns in the data model and columns model 数据模型和列模型中的列数相等
  • Using setAutoCreateColumnsFromModel(false) to avoid recreation of columns model when a column is added or removed by the data/table model 使用setAutoCreateColumnsFromModel(false)来避免在数据/表模型添加或删除列时重新创建列模型
  • Ability to move columns 能够移动列

The large button adds a new column to the end. 大按钮在末尾添加一个新列。 Each new column gets a unique identifier. 每个新列都获得一个唯一标识符。

在此输入图像描述

The header has a right click menu HeaderMenu to remove columns. 标题有一个右键菜单HeaderMenu来删除列。 A timer calls table.tableModel.addRow() to insert a new row on the top. 计时器调用table.tableModel.addRow()在顶部插入一个新行。 The data for each column is generated by class Column . 每列的数据由类Column生成。 In this demo the value is simply a rows counter with column's identifier. 在此演示中,值只是一个带有列标识符的行计数器。

在此输入图像描述

In the actual table (not this demo) 在实际表中(不是这个演示)

  • each column is a subclass of Column and generates meaningful data 每列都是Column的子类,并生成有意义的数据
  • the menu also contains insert left/right and replace. 菜单还包含左/右插入和替换。 This is achieved using similar as in the demo add/remove methods and by moving a column to the desired position 这可以使用类似于演示添加/删除方法以及将列移动到所需位置来实现
  • the data model may contain a dozen of columns and over a million rows 数据模型可能包含十几列和一百多万行
  • rows may be added with a time interval between a millisecond to several seconds, ie performance matters 可以以毫秒到几秒之间的时间间隔添加行,即性能很重要

This demo demonstrates the problem with deletion of columns which generates errors like this: 此演示演示了删除列的问题,这些列会生成如下错误:

Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: 2 >= 2
at java.util.Vector.elementAt(Vector.java:477)
at javax.swing.table.DefaultTableModel.getValueAt(DefaultTableModel.java:649)
at javax.swing.JTable.getValueAt(JTable.java:2720)
at javax.swing.JTable.prepareRenderer(JTable.java:5712)
at javax.swing.plaf.basic.BasicTableUI.paintCell(BasicTableUI.java:2114)
...

Please advise how to fix it. 请告知如何解决它。 Here is the entire code: 这是整个代码:

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;

@SuppressWarnings("serial")
public class TableDemo extends JTable {

    private static class Column {

        private int rowsCounter = 0;
        private final String identifier;

        public Column(String identifier) {
            this.identifier = identifier;
        }

        private String nextCellValue() {
            return (rowsCounter++) + ", id: " + identifier;
        }
    }

    private static class MyTableModel extends DefaultTableModel {

        private final List<Column> columns = new ArrayList<>();
        private int nextColumnIdentifier = 0;

        private void addRow() {
            Object[] row = columns.stream().map(Column::nextCellValue).toArray();
            insertRow(0, row);
        }

        private TableColumn addColumn() {
            String identifier = String.valueOf(nextColumnIdentifier++);
            columns.add(new Column(identifier));
            addColumn(identifier);
            TableColumn tc = new TableColumn();
            tc.setIdentifier(identifier);
            tc.setHeaderValue(identifier);
            tc.setModelIndex(columns.size() - 1);
            return tc;
        }

        private void removeColumn(int idx) {
            columns.remove(idx);
            columnIdentifiers.remove(idx);
            for (Object row : dataVector) {
                ((Vector<?>) row).remove(idx);
            }
            fireTableStructureChanged();
        }
    }

    private static class HeaderMenu extends JPopupMenu {

        private int columnViewIndex;

        private HeaderMenu(final TableDemo table) {
            JMenuItem item = new JMenuItem("Delete column");
            item.addActionListener(e -> table.deleteColumn(columnViewIndex));
            add(item);

            final MouseAdapter ma = new MouseAdapter() {

                boolean dragged = false;

                @Override
                public void mouseReleased(MouseEvent e) {
                    if (!dragged && e.getButton() == MouseEvent.BUTTON3) {
                        final Point p = e.getPoint();
                        SwingUtilities.invokeLater(() -> {
                            columnViewIndex = table.columnAtPoint(p);
                            show(e.getComponent(), p.x, p.y);
                        });
                    }
                    dragged = false;
                }

                @Override
                public void mouseDragged(MouseEvent e) {
                    dragged = true;
                }
            };

            table.getTableHeader().addMouseListener(ma);
            table.getTableHeader().addMouseMotionListener(ma);
        }
    }

    private MyTableModel tableModel = new MyTableModel();

    private TableDemo() {
        new HeaderMenu(this);
        setModel(tableModel);
        setAutoCreateColumnsFromModel(false);
        setDefaultEditor(Object.class, null);
    }

    private void addColumn() {
        TableColumn tc = tableModel.addColumn();
        addColumn(tc);
    }

    void deleteColumn(int idxView) {
        TableColumn tc = getColumnModel().getColumn(idxView);
        tableModel.removeColumn(tc.getModelIndex());
        removeColumn(tc);
    }

    private static void buildAndShowGui() {
        TableDemo table = new TableDemo();
        table.setPreferredScrollableViewportSize(new Dimension(800, 300));
        table.setFillsViewportHeight(true);
        JScrollPane tableScrollPane = new JScrollPane(table);
        JButton buttonAdd = new JButton("Add column");
        buttonAdd.addActionListener(e -> table.addColumn());
        int gaps = 10;
        JPanel panel = new JPanel(new BorderLayout(gaps, gaps));
        panel.setBorder(BorderFactory.createEmptyBorder(gaps, gaps, gaps, gaps));
        panel.add(buttonAdd, BorderLayout.NORTH);
        panel.add(tableScrollPane, BorderLayout.CENTER);
        JFrame frame = new JFrame(table.getClass().getSimpleName());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setContentPane(panel);
        frame.pack();
        frame.setVisible(true);

        new Timer().schedule(new TimerTask() {

            @Override
            public void run() {
                SwingUtilities.invokeLater(() -> table.tableModel.addRow());
            }
        }, 500, 100);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> buildAndShowGui());
    }
}

The problem is that after removal of a column from some modelIndex in the table/data model, the TableColumn#getModelIndex() of some of the columns in the columns model may become shifted by 1. Here is an example, suppose the table has 3 columns and the code below produces 0 1 2 : 问题是在从表/数据模型中的某些modelIndex中删除列之后,列模型中某些列的TableColumn#getModelIndex()可能会移位1.这是一个示例,假设表有3个列和下面的代码生成0 1 2

for (int i = 0; i < getColumnModel().getColumnCount(); i++) {
    System.out.print(getColumnModel().getColumn(i).getModelIndex() + " ");
}

Then after removal of the column 1 from both data model and column model, the output of this code becomes: 0 2 . 然后从数据模型和列模型中删除列1后,此代码的输出变为: 0 2 Therefore JTable produces ArrayIndexOutOfBoundsException when accessing non-existing column 2 in the data model. 因此,当访问数据模型中不存在的第2列时, JTable会生成ArrayIndexOutOfBoundsException This is why removal of the last column didn't generate an error. 这就是删除最后一列没有产生错误的原因。

The solution is to shift the modelIndex of the columns on the right side of the removed column. 解决方案是移动已删除列右侧列的modelIndex This can be done with a custom TableColumnModel: 这可以使用自定义TableColumnModel完成:

private static class MyColumnsModel extends DefaultTableColumnModel {

    private TableColumn deleteColumn(int idxView) {
        if (selectionModel != null) {
            selectionModel.removeIndexInterval(idxView, idxView);
        }
        TableColumn tc = tableColumns.remove(idxView);
        tc.removePropertyChangeListener(this);
        for (TableColumn tableColumn : tableColumns) {
            if (tableColumn.getModelIndex() > tc.getModelIndex()) {
                tableColumn.setModelIndex(tableColumn.getModelIndex() - 1);
            }
        }
        return tc;
    }
}

And with add / remove methods of the table as following: 并使用表的添加/删除方法如下:

private void addColumn() {
    TableColumn tc = tableModel.addColumn();
    addColumn(tc); // equal to columnsModel.addColumn(tc);
}

private void deleteColumn(int idxView) {
    TableColumn tc = columnsModel.deleteColumn(idxView);
    tableModel.removeColumn(tc.getModelIndex());
}

Here is the entire fixed code: 这是整个固定代码:

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;

@SuppressWarnings("serial")
public class TableDemo extends JTable {

    private static class Column {

        private int rowsCounter = 0;
        private final String identifier;

        public Column(String identifier) {
            this.identifier = identifier;
        }

        private String nextCellValue() {
            return (rowsCounter++) + ", id: " + identifier;
        }
    }

    private static class MyTableModel extends DefaultTableModel {

        private final List<Column> columns = new ArrayList<>();
        private int nextColumnIdentifier = 0;

        private void addRow() {
            Object[] row = columns.stream().map(Column::nextCellValue).toArray();
            insertRow(0, row);
        }

        private TableColumn addColumn() {
            String identifier = String.valueOf(nextColumnIdentifier++);
            columns.add(new Column(identifier));
            addColumn(identifier);
            TableColumn tc = new TableColumn();
            tc.setIdentifier(identifier);
            tc.setHeaderValue(identifier);
            tc.setModelIndex(columns.size() - 1);
            return tc;
        }

        private void removeColumn(int idx) {
            columns.remove(idx);
            columnIdentifiers.remove(idx);
            for (Object row : dataVector) {
                ((Vector<?>) row).remove(idx);
            }
            fireTableStructureChanged();
        }
    }

    private static class MyColumnsModel extends DefaultTableColumnModel {

        private TableColumn deleteColumn(int idxView) {
            if (selectionModel != null) {
                selectionModel.removeIndexInterval(idxView, idxView);
            }
            TableColumn tc = tableColumns.remove(idxView);
            tc.removePropertyChangeListener(this);
            for (TableColumn tableColumn : tableColumns) {
                if (tableColumn.getModelIndex() > tc.getModelIndex()) {
                    tableColumn.setModelIndex(tableColumn.getModelIndex() - 1);
                }
            }
            return tc;
        }
    }

    private static class HeaderMenu extends JPopupMenu {

        private int columnViewIndex;

        private HeaderMenu(final TableDemo table) {
            JMenuItem item = new JMenuItem("Delete column");
            item.addActionListener(e -> table.deleteColumn(columnViewIndex));
            add(item);

            final MouseAdapter ma = new MouseAdapter() {

                boolean dragged = false;

                @Override
                public void mouseReleased(MouseEvent e) {
                    if (!dragged && e.getButton() == MouseEvent.BUTTON3) {
                        final Point p = e.getPoint();
                        SwingUtilities.invokeLater(() -> {
                            columnViewIndex = table.columnAtPoint(p);
                            show(e.getComponent(), p.x, p.y);
                        });
                    }
                    dragged = false;
                }

                @Override
                public void mouseDragged(MouseEvent e) {
                    dragged = true;
                }
            };

            table.getTableHeader().addMouseListener(ma);
            table.getTableHeader().addMouseMotionListener(ma);
        }
    }

    private MyTableModel tableModel = new MyTableModel();
    private MyColumnsModel columnsModel = new MyColumnsModel();

    private TableDemo() {
        new HeaderMenu(this);
        setModel(tableModel);
        setColumnModel(columnsModel);
        setAutoCreateColumnsFromModel(false);
        setDefaultEditor(Object.class, null);
    }

    private void addColumn() {
        TableColumn tc = tableModel.addColumn();
        addColumn(tc); // equal to columnsModel.addColumn(tc);
    }

    private void deleteColumn(int idxView) {
        TableColumn tc = columnsModel.deleteColumn(idxView);
        tableModel.removeColumn(tc.getModelIndex());
    }

    private static void buildAndShowGui() {
        TableDemo table = new TableDemo();
        table.setPreferredScrollableViewportSize(new Dimension(800, 300));
        table.setFillsViewportHeight(true);
        JScrollPane tableScrollPane = new JScrollPane(table);
        JButton buttonAdd = new JButton("Add column");
        buttonAdd.addActionListener(e -> table.addColumn());
        int gaps = 10;
        JPanel panel = new JPanel(new BorderLayout(gaps, gaps));
        panel.setBorder(BorderFactory.createEmptyBorder(gaps, gaps, gaps, gaps));
        panel.add(buttonAdd, BorderLayout.NORTH);
        panel.add(tableScrollPane, BorderLayout.CENTER);
        JFrame frame = new JFrame(table.getClass().getSimpleName());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setContentPane(panel);
        frame.pack();
        frame.setVisible(true);

        new Timer().schedule(new TimerTask() {

            @Override
            public void run() {
                SwingUtilities.invokeLater(() -> table.tableModel.addRow());
            }
        }, 500, 100);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> buildAndShowGui());
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM