0

I'm trying to create a dynamic JTable in Java Swing where users can add/delete rows and columns as they want. Everything is good, except for deleting the column. I've been at this for hours now and I can't figure out how to completely erase a column and its values.

There are removeColumn(int) methods in the classes, but they only hide the column from view. I've also searched and found this answer, but when I try to create the CustomTableModel class and extend DefaultTableModel, I just get a ClassCastException.

Wayne G
  • 1
  • 1
  • 2
    *I just get a ClassCastException.* - well then your code is wrong. We can't guess why you get a ClassCastException. Post your [mre] demonstrating the problem. Your custom model should only implement the "removeCoumn(...)" method for the purpose of the MRE. – camickr Dec 22 '21 at 21:14
  • 1
    1. Remove the data from the model; 2. Update the `TableModel` to remove the column from related methods; 3. Reapply the table model to the table if required. You might be able to get away with; 1 & 2 if you ensure that your also triggering the `tableStructureChanged` event – MadProgrammer Dec 22 '21 at 21:18
  • @camickr my bad, should've made it more specific. I've created my CustomTableModel class and extended it to DefaultTableModel correctly, but when I try to assign and cast the table model to it using `(CustomTableModel) table.getModel();`, I get the error. Though I'm not sure if this is the correct way to retrieve the object. – Wayne G Dec 22 '21 at 21:20
  • 2
    If you set the model of the table to be the CustomTableModel then you should be able to use the getModel() method and cast it. If you get an exception then that means you did not set the CustomTableModel to the table. Still don't see an [mre]. Add a `System.out.println( table.getModel() )` statement to your code to see what the class the model is. – camickr Dec 22 '21 at 21:22
  • @camickr wow... I never realized I had to set the model too. I'm using netbeans so I had the table autogenerated, but now I built it manually and set the model to CustomTableModel, and it worked. I never really delved deep into typecasting so I didn't know about this. Thank you so much for helping, and thanks for this hard-taught lesson. – Wayne G Dec 22 '21 at 21:29
  • 1
    @WayneG glad it helped. Note, we don't use the IDE to generate code. You spend time learning the IDE and not Java/Swing. Try creating your forms manually. You will be in full control of the code and layout managers used. The IDE can still be used for debugging and testing. – camickr Dec 22 '21 at 21:31
  • @camickr will do, thanks for the extra lesson :) – Wayne G Dec 22 '21 at 21:35

2 Answers2

1

Adding a column

  • Determine the new index of the column (how you do this is up to you)
  • Determine the new values for each row/column - you could also just as easily set the values to null to start with
  • Update the row data in the model
  • Update the column data in the model (ie columnCount and columnName)
  • Trigger a TableModelEvent.HEADER_ROW event so that the JTable updates itself with the new data

Removing a column

  • Determine the column index to be removed
  • Remove the column data for each row
  • Update the column data in the model (ie columnCount and columnName)
  • Trigger a TableModelEvent.HEADER_ROW event so that the JTable updates itself with the new data

Runnable example

This is a basic example of the concept. Personally I prefer to manage my data via POJOs (which is here the idea of "hiding columns" would be more practical, but that might not always be possible

enter image description here

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private MutableTableModel model;
        private JTable table;

        public TestPane() {
            setLayout(new BorderLayout());

            JButton addColumnButton = new JButton("Add column");
            JButton removeColumnButton = new JButton("Remove column");

            addColumnButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    List<Object> rows = new ArrayList<>(100);
                    for (int row = 0; row < 100; row++) {
                        rows.add(randomString());
                    }

                    String columnName = JOptionPane.showInputDialog(TestPane.this, "Column name");
                    if (columnName == null || columnName.isBlank()) {
                        columnName = randomString();
                    }

                    int columnIndex = table.getSelectedColumn();
                    if (columnIndex < 0) {
                        model.addColumn(columnName, rows);
                    } else {
                        model.addColumn(columnName, rows, columnIndex + 1);
                    }
                }
            });
            removeColumnButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    int columnIndex = table.getSelectedColumn();
                    if (columnIndex < 0) {
                        JOptionPane.showMessageDialog(TestPane.this, "Now column selected");
                    } else {
                        model.removeColumn(columnIndex);
                    }
                }
            });

            JPanel actionPanels = new JPanel(new GridBagLayout());
            actionPanels.add(addColumnButton);
            actionPanels.add(removeColumnButton);

            add(actionPanels, BorderLayout.SOUTH);

            String[] columnNames = new String[]{"One", "Two", "Three", "Four", "Five"};
            List<List<Object>> rows = new ArrayList<>(100);
            for (int row = 0; row < 100; row++) {
                List<Object> rowData = new ArrayList<>(columnNames.length);
                for (int col = 0; col < columnNames.length; col++) {
                    rowData.add(randomString());
                }
                rows.add(rowData);
            }

            model = new MutableTableModel(Arrays.asList(columnNames), rows);
            table = new JTable(model);

            add(new JScrollPane(table));
        }

        private Random random = new Random();

        protected String randomString() {
            int leftLimit = 48; // numeral '0'
            int rightLimit = 122; // letter 'z'
            int targetStringLength = 10;

            String generatedString = random.ints(leftLimit, rightLimit + 1)
                    .filter(i -> (i <= 57 || i >= 65) && (i <= 90 || i >= 97))
                    .limit(targetStringLength)
                    .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
                    .toString();

            return generatedString;
        }
    }

    public class MutableTableModel extends AbstractTableModel {

        private ArrayList<List<Object>> rows = new ArrayList<>(32);
        private ArrayList<String> columnNames = new ArrayList<>(8);

        public MutableTableModel() {
        }

        public MutableTableModel(List<String> columnNames, List<List<Object>> rows) {
            this.columnNames = new ArrayList<>(columnNames);
            this.rows = new ArrayList<>(rows);
        }

        public void addColumn(String name) {
            addColumn(name, columnNames.size() - 1);
        }

        public void addColumn(String name, int columnIndex) {
            columnIndex = Math.max(0, Math.min(columnIndex, columnNames.size()));
            int rowCount = getRowCount();
            List<Object> rows = new ArrayList<>(rowCount);
            for (int row = 0; row < rowCount; row++) {
                rows.add(null);
            }

            addColumn(name, rows, columnIndex);
        }

        public void addColumn(String name, List<Object> rows) {
            addColumn(name, rows, columnNames.size());
        }

        public void addColumn(String name, List<Object> newRows, int columnIndex) {
            columnIndex = Math.max(0, Math.min(columnIndex, columnNames.size()));
            columnNames.add(columnIndex, name);

            int rowCount = getRowCount();
            for (int row = 0; row < rowCount; row++) {
                List<Object> rowData = rows.get(row);
                Object value = null;
                if (row < newRows.size()) {
                    value = newRows.get(row);
                }
                rowData.add(columnIndex, value);
            }

            fireTableStructureChanged();
        }

        public void removeColumn(int columnIndex) {
            if (columnIndex < 0 || columnIndex >= columnNames.size()) {
                return;
            }
            int rowCount = getRowCount();
            for (int row = 0; row < rowCount; row++) {
                List<Object> rowData = rows.get(row);
                rowData.remove(columnIndex);
            }

            columnNames.remove(columnIndex);

            fireTableStructureChanged();
        }

        @Override
        public int getRowCount() {
            return rows.size();
        }

        @Override
        public int getColumnCount() {
            return columnNames.size();
        }

        @Override
        public String getColumnName(int column) {
            if (column >= columnNames.size()) {
                return super.getColumnName(column);
            }
            return columnNames.get(column);
        }

        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            return rows.get(rowIndex).get(columnIndex);
        }

    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
0

Thanks to camickr for knocking my head back in place, I really just had to set the model to CustomTableModel so I could cast it back using getModel() as well. It now works using the answer in here. If you're using NetBeans, be sure to edit the autogenerated code yourself and change...

contentTable.setModel(new TableModel(
    new Object [][] {

    },
    new String [] {

    }
));

to

contentTable.setModel(new CustomTableModel(
    new Object [][] {

    },
    new String [] {

    }
));
Wayne G
  • 1
  • 1
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Dec 23 '21 at 00:09