javaswingjtableabstracttablemodel

Any idea why AbstractTableModel causes empty cells to be added to my table? (Java Swing)


As the title says, empty cells are being added to my table when I add something to the underlying list.

Main Class:

import javax.swing.SwingUtilities;

public class Main {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                MainFrame frame = new MainFrame();
            }
        });
    }
}

MainFrame class:

import java.awt.BorderLayout;
import java.util.ArrayList;

import javax.swing.JFrame;
import javax.swing.JTable;

public class MainFrame extends JFrame {
    private JTable table;
    private ArrayList<String> strings;
    
    public MainFrame() {
        setTitle("Stack Overflow");
        setSize(800, 800);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLayout(new BorderLayout());
        setVisible(true);
        
        strings = new ArrayList<String>();
        table = new JTable(new TableModel(strings));
        
        add(table,BorderLayout.CENTER);
        
        for (int i = 0; i < 10; i++) {
            strings.add("data");
        }
    }
}

AbstractTableModel Class:

import java.util.List;

import javax.swing.table.AbstractTableModel;

public class TableModel extends AbstractTableModel {
    private String[] colNames = {"Col1","Col2"};
    
    private List<String> strings;
    
    public TableModel(List<String> strings) {
        this.strings = strings;
    }
    
    @Override
    public String getColumnName(int column) {
        return colNames[column];
    }

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

    @Override
    public int getColumnCount() {
        return 2;
    }

    @Override
    public String getValueAt(int rowIndex, int columnIndex) {
        if (rowIndex*2 + columnIndex >= strings.size()) return "";
        return strings.get(rowIndex*2 + columnIndex);
    }
}

Is there a better way of getting the items from my 1D array so that the table is populated from left to right, top to bottom (without the extra empty cells)?

Why does this problem happen anyway?

Result: Extra empty rows added.


Solution

  • The data you have and the way you model it are two different concerns, don't be afraid to seperate them to achieve your goals, for example:

    import java.awt.BorderLayout;
    import java.awt.EventQueue;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.util.ArrayList;
    import java.util.List;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.JScrollPane;
    import javax.swing.JTable;
    import javax.swing.Timer;
    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 JTable table;
            private SequentialTableModel model;
    
            public TestPane() {
                setLayout(new BorderLayout());
                model = new SequentialTableModel();
                table = new JTable(model);
                add(new JScrollPane(table));
    
                Timer timer = new Timer(500, new ActionListener() {
                    private int count = 0;
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        model.add("Data " + (++count));
                    }
                });
                timer.start();
            }
    
        }
    
        public class SequentialTableModel extends AbstractTableModel {
    
            private String[] colNames = {"Col1", "Col2"};
    
            private List<List<String>> rows = new ArrayList<>(32);
    
            public SequentialTableModel() {
            }
    
            @Override
            public String getColumnName(int column) {
                return colNames[column];
            }
    
            @Override
            public int getRowCount() {
                return rows.size();
            }
    
            @Override
            public int getColumnCount() {
                return 2;
            }
    
            @Override
            public String getValueAt(int rowIndex, int columnIndex) {
                List<String> values = rows.get(rowIndex);
                if (columnIndex >= values.size()) {
                    return null;
                }
                return values.get(columnIndex);
            }
    
            public void add(String value) {
                if (rows.isEmpty()) {
                    addNewRow(value);
                    return;
                }
    
                List<String> lastRow = rows.get(rows.size() - 1);
                if (lastRow.size() < 2) {
                    lastRow.add(value);
                    fireTableCellUpdated(rows.size() - 1, lastRow.size() - 1);
                } else {
                    addNewRow(value);
                }
            }
    
            protected void addNewRow(String value) {
                List<String> row = new ArrayList<>(2);
                row.add(value);
                rows.add(row);
                fireTableRowsInserted(rows.size() - 1, rows.size() - 1);
                return;
            }
        }
    }