Calling Previous Model Behavior of JTable.setModel()

Navnath picture Navnath · Apr 16, 2013 · Viewed 26.5k times · Source

Have one JTable on my swing screen. While loading screen, I am setting one table model which is created for this table only. And at run time if there is any data change I am recreating the same model and again set it using objJTable.setModel(objCustTableModel).

Now the problem is table model which is loaded at the time of screen load, same model object get called while setting objJTable.setModel(objCustTableModel) at run time which called getColumnClass(int col) method from CustTableModel class. After this object call, my new model object get called. Again if I set another new table model, by using same code objJTable.setModel(objCustTableModel2), model call's functions get called first from objCustTableModel and then objCustTableModel2.

In short setModel() function first call previous model object and then current model object. How can I restrict to call of previous model object?

For example

import java.awt.Cursor;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;


/**
 * Steps to reproduce issue.
 * 1. Run this program.
 * 2. There will be two rows in table. Delete all rows one by one.
 * 3. Now click on Add button and see the exception.
 * 
 * I come to know that this exception is because of table.setAutoCreateRowSorter(true); line, which is there in TestCustTableModel's constructor
 * If you comment this line, then issue got resolved.
 * But I want to apply sorting on columns so this line is required.
 */

public class TestCustTableModel extends JPanel{
    JTable table = new JTable();
    public TestCustTableModel() {

        Object[][] data = new Object[2][3];
        data[0][0] = "1";
        data[0][1] = "User1";
        data[0][2] = "Delete";

        data[1][0] = "2";
        data[1][1] = "User2";
        data[1][2] = "Delete";

        JButton addButton = new JButton("Add");
        addButton.addMouseListener(new AddListener());
        table.setModel(new CustModel(data));
        table.addMouseListener(new TableListener());

        /**#################################
         * Following line throws ArrayIndexOutOfBoundsException. Please comment or Uncomment following line and see the difference.
         */
        table.setAutoCreateRowSorter(true);


        table.getTableHeader().setCursor(new Cursor(Cursor.HAND_CURSOR));
        JScrollPane scrollPane = new JScrollPane(table);

        this.add(addButton);
        this.add(scrollPane);
    }


    class TableListener extends MouseAdapter {
        public void mouseClicked(MouseEvent evnt) {
            Point p = evnt.getPoint();
            if(table.columnAtPoint(p) == 2) {
                Object[][] data = null;
                if(table.getModel().getRowCount() == 2 && table.rowAtPoint(p) == 0) {
                    data = new Object[1][3];
                    data[0][0] = "2";
                    data[0][1] = "User2";
                    data[0][2] = "Delete";
                }else if(table.getModel().getRowCount() == 2 && table.rowAtPoint(p) == 1) {
                    data = new Object[1][3];
                    data[0][0] = "1";
                    data[0][1] = "User1";
                    data[0][2] = "Delete";
                }else {
                    data = new Object[0][];
                }
                table.setModel(new CustModel(data));
            }
        }
    }


    class AddListener extends MouseAdapter{

        @Override
        public void mouseClicked(MouseEvent evnt) {
            if(table.getModel().getRowCount() == 2) {
                return;
            }
            Object[][] data = new Object[table.getModel().getRowCount() + 1][3];
            for(int i = 0; i <= table.getModel().getRowCount(); i++) {
                data[i][0] = i;
                data[i][1] = "User" + i;
                data[i][2] = "Delete";
            }
            table.setModel(new CustModel(data));
        }

    }

    class CustModel extends AbstractTableModel {
        private String[] columnNames = {"ID", "NAME", "DELETE"};
        private Object[][] data = null;

        public CustModel(Object[][] data) {
            this.data = data;
        }
        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 Class getColumnClass(int col) {
            return getValueAt(0, col).getClass();
        }
    }

    private void display() {
        JFrame f = new JFrame("SwapTableModel");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(this);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    public static void main(String[] args) {
        TestCustTableModel obj = new TestCustTableModel();
        obj.display();
    }
}

at this time swing first internally called getColumnClass function on the basis of objCustTableModel and after that using objCustTableModel2 object. I want to restrict call which is on the basis of objCustTableModel.

Answer

trashgod picture trashgod · Apr 16, 2013

Starting from this example, I saw no unexpected behavior with the getColumnClass() implementation shown below.

private Model() {
    final Object[] data = {this.toString()};
    this.model = new DefaultTableModel(data, 1){

        @Override
        public Class<?> getColumnClass(int columnIndex) {
            System.out.println(data[0]);
            return super.getColumnClass(columnIndex);
        }
    };
    model.addRow(data);
}

Note that JTable may invoke getColumnClass() anytime it determines that a cell needs rendering. If necessary, you can use EventQueue.invokeLater() to schedule something that will "happen after all pending events are processed."

EventQueue.invokeLater(new Runnable() {
    @Override
    public void run() {
        // do something
    }
});

Addenda:

  • If getAutoCreateRowSorter() is true, setModel() tries to restore the RowSorter created using the old model.

  • You can specify setAutoCreateRowSorter(true) after you change models, as shown below, or extend your TableModel to update the model in place, as shown here.

  • Use ActionListener for JButton instead of MouseListener.

  • Use a TableCellEditor for buttons in the table, as shown here and here.

Code:

addButton.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        if (table.getModel().getRowCount() == 2) {
            return;
        }
        Object[][] data = new Object[table.getModel().getRowCount() + 1][5];
        table.setRowSorter(null);
        for (int i = 0; i <= table.getModel().getRowCount(); i++) {
            data[i][0] = i;
            data[i][6] = "User" + i;
            data[i][7] = "Delete";
        }
        table.setModel(new CustModel(data));
        table.setAutoCreateRowSorter(true);
    }
});