Home > Enterprise >  Copying (some columns of) a JTable to a new JTable
Copying (some columns of) a JTable to a new JTable

Time:08-24

I have a JTable displayed in a frame. This table must remain unmodified for later processing. At some point I want to show a column reduced version of this table in a dialog. So I make a copy of the original table in creating a new TableModel and TableColumnModel, thereby skipping the columns not needed.

Everything is fine until the dialog is set to visible. Then I run into an endless error loop, starting with an "ArrayIndexOutOfBoundsException: 3 >= 2". If 3 is the index to a table row, then it's indeed too large. As the reduced table has only two columns, a column index of 2 would already exceed. Checking the columns with getColumnCount() shows that the values are ok.
I suspect my copyTable method to be the culprit, but have no idea where the 3 index is coming from.

Instead of filling the rows of the table's model one by one, I succeeded using DefaultTableModel.addColumn(Object columnName, Object[] columnData) as suggested here. Then, however, I lose the columns' width information. Hence I would still like to know my fault in the current code.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.table.*;

public class CopyTable extends JFrame {
  public static final long serialVersionUID = 100L;

  public CopyTable() {
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setLocationRelativeTo(null);

    JTable table= createTable();
    JScrollPane scroll= new JScrollPane(table);
    add(scroll, BorderLayout.CENTER);
    JButton b= new JButton("Show dialog");
    b.addActionListener(e -> createAndShowDialog(table));
    add(b, BorderLayout.SOUTH);
    setSize(table.getColumnModel().getTotalColumnWidth() 11, 240);
    setVisible(true);
  }


  static public void main(String args[]) {
    EventQueue.invokeLater(CopyTable::new);
  }


// @param columns   Indices of columns to be copied into the new table.
  public JTable copyTable(JTable table, int... columns) {
    DefaultTableModel tblModel= new DefaultTableModel(0, columns.length);
    DefaultTableModel oldModel= (DefaultTableModel)table.getModel();
    Object[] row= new Object[columns.length];
    int iRow= 0;
    while (iRow<oldModel.getRowCount()) {
      for (int i=0; i<columns.length; i  ) {
        row[i]= oldModel.getValueAt(iRow, columns[i]);
      }
      tblModel.addRow(row);
      iRow  ;
    }
    DefaultTableColumnModel colModel= new DefaultTableColumnModel();
    DefaultTableColumnModel oldColModel=
                (DefaultTableColumnModel)table.getColumnModel();
    for (int i=0; i<columns.length; i  ) {
      colModel.addColumn(oldColModel.getColumn(columns[i]));
/*      Creating a new column didn't help.
      TableColumn tc= new TableColumn();
      tc= oldColModel.getColumn(columns[i]);
      colModel.addColumn(tc);
*/
    }
    return new JTable(tblModel, colModel);
  }


  private void createAndShowDialog(JTable table) {
    JTable tbl = copyTable(table, 0, 3); // Copy only columns 0 and 3.
    JOptionPane.showMessageDialog(this, "" tbl.getModel().getRowCount() ", " 
            tbl.getModel().getColumnCount() ", " 
            tbl.getColumnModel().getColumnCount(),
            "Checking row/column count",
            JOptionPane.INFORMATION_MESSAGE);
    JDialog dlg= new JDialog(this, "Reduced table", true);
    dlg.setLocationRelativeTo(this);
    JScrollPane scroll = new JScrollPane(tbl);
    dlg.add(scroll, BorderLayout.CENTER);
    dlg.pack();
    dlg.setVisible(true);
  }


  private JTable createTable() {
    String headers[] = {"Fruit", "Colour", "Count", "Price"};
    Object data[][] = {
        {"Apple", "Green", 6, .3},
        {"Banana", "Yellow", 3, .4},
        {"Cherry", "Red", 10, 1.1}
      };

    DefaultTableModel model = new DefaultTableModel(data, headers) {
      public Class<?> getColumnClass(int column) {
        Class<?> returnValue;
        if (column>=0 && column<getColumnCount() && getValueAt(0,column)!=null)
          returnValue= getValueAt(0, column).getClass();
        else
          returnValue= Object.class;
        return returnValue;
      }
    };
    JTable tbl= new JTable(model);
    TableColumnModel tcm= tbl.getColumnModel();
    int[] width= new int[] {60, 50, 40, 40};
    for (int i=0; i<headers.length; i  ) {
      tcm.getColumn(i).setPreferredWidth(width[i]);
      tcm.getColumn(i).setWidth(width[i]);
    }
    return tbl;
  }

}
Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: 3 >= 2
        at java.base/java.util.Vector.elementAt(Vector.java:466)
        at java.desktop/javax.swing.table.DefaultTableModel.getValueAt(DefaultTableModel.java:661)
        at java.desktop/javax.swing.JTable.getValueAt(JTable.java:2763)
        at java.desktop/javax.swing.JTable.prepareRenderer(JTable.java:5780)
        at java.desktop/javax.swing.plaf.basic.BasicTableUI.paintCell(BasicTableUI.java:2207)
        at java.desktop/javax.swing.plaf.basic.BasicTableUI.paintCells(BasicTableUI.java:2109)
        at java.desktop/javax.swing.plaf.basic.BasicTableUI.paint(BasicTableUI.java:1905)
        at java.desktop/javax.swing.plaf.ComponentUI.update(ComponentUI.java:161)
        at java.desktop/javax.swing.JComponent.paintComponent(JComponent.java:852)

java 18

CodePudding user response:

@g00se Your solution works. Thank you. The only drawback is that removing columns from the TableColumnModel doesn't remove them from the TableModel. So I added the following to your code

        int icnt= oldModel.getRowCount();
        for (int j=0; j<icnt; j  ) {
          Vector<?> vec= (Vector<?>)data.get(j);
          for (int i=columns.length-1; i>=0; i--) {
            vec.removeElementAt(columns[i]);
          }
          data.setElementAt(vec, j);
        }
        ((DefaultTableModel)result.getModel()).setDataVector(data, headers);
        return result;

But due to lack of Generics knowledge, I cannot make the code compile. Still an idea for that?
Just copying/creating a new model would avoid any removal. So I am still curious as for my original error.

CodePudding user response:

I can't say I know quite why you get that exception, but this is a possible problem-free alternative, involving cloning the table then removing unwanted columns. This is based on the intuitive ascending ordering of required columns.

    // @param columns Indices of columns to be copied into the new table.
    public JTable copyTable(JTable table, int... columns) {
        DefaultTableModel oldModel = (DefaultTableModel) table.getModel();
        @SuppressWarnings("unchecked")
        Vector<? extends Vector> data = (Vector<? extends Vector>) oldModel.getDataVector().clone();
        Vector<Object> headers = new Vector<>();
        TableColumnModel cmSource = table.getColumnModel();
        for (int i = 0; i < table.getColumnModel().getColumnCount(); i  ) {
            headers.add(cmSource.getColumn(i).getIdentifier());
        }
        JTable result = new JTable(data, headers);
        TableColumnModel cmTarget = result.getColumnModel();
        Set<Integer> columnsToCopy = new HashSet<>();
        for (int i = columns.length; --i >= 0;) {
            columnsToCopy.add(columns[i]);
        }
        for (int i = cmTarget.getColumnCount(); --i >= 0;) {
            if (!columnsToCopy.contains(i)) {
                cmTarget.removeColumn(cmTarget.getColumn(i));
            }
        }
        return result;
    }

  • Related