Search code examples
javaswingjtablelistselectionlistener

Deleting row from JTable after valueChanged event is triggered


I'm using ListSelectionListener to update my JTextField (countryTxt) from selected row.

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableModel;

public class App {

    JFrame frame = new JFrame();
    JTable table = new JTable();
    DefaultTableModel model = new DefaultTableModel(new Object[][] {},
            new String[] { "Country", "City", "Street" });
    JButton button = new JButton("Remove");
    JTextField countryTxt = new JTextField();

    int row;

    public App() {
        table.setModel(model);
        data();
        table.getSelectionModel().addListSelectionListener(
                new ListSelectionListener() {
                    @Override
                    public void valueChanged(ListSelectionEvent e) {
                        if (!e.getValueIsAdjusting()) {
                            row = table.getSelectedRow();
                            countryTxt.setText((String) model
                                    .getValueAt(row, 0));
                        }
                    }
                });
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent arg0) {
                model.removeRow(row);
            }
        });
        frame.add(countryTxt,BorderLayout.NORTH);
        frame.add(new JScrollPane(table), BorderLayout.CENTER);
        frame.add(button, BorderLayout.SOUTH);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
        frame.setLocationRelativeTo(null);
    }

    public void data() {
        model.addRow(new String[] { "USA", "New York", "First street" });
        model.addRow(new String[] { "Russia", "Moscow", "Second street" });
        model.addRow(new String[] { "Japan", "Osaka", "Osaka street" });
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new App();
            }
        });
    }
}

But when I select a row and click a button it trows me and ArrayIndexOutOfBounds exception. When I don't select a row in my table and click a button everything works fine. Obviously I can delete a row when valueChanged event is not triggered. So my question is: How to delete a row after valueChanged event is triggered. Thanks in advance.


Solution

  • I had to track down a similar problem involving list removal a while ago. The main issue here is that the button listener's call to model.removeRow(row) was sending a valueChanged event to the model's selection listener, which would then attempt to update the text field using a nonexistent selection (i.e. a list index of -1). I've made these fixes to your code, and the relevant sections are marked with comments. This code allows items to be selected/deleted without throwing an exception.

    import java.awt.BorderLayout;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JScrollPane;
    import javax.swing.JTable;
    import javax.swing.JTextField;
    import javax.swing.SwingUtilities;
    import javax.swing.event.ListSelectionEvent;
    import javax.swing.event.ListSelectionListener;
    import javax.swing.table.DefaultTableModel;
    
    public class App {
        JFrame frame = new JFrame();
        DefaultTableModel model = new DefaultTableModel(new Object[][] {},
                new String[] { "Country", "City", "Street" });
        JTable table = new JTable(model);
        JButton button = new JButton("Remove");
        JTextField countryTxt = new JTextField();
    
        public App() {
            data();
            table.getSelectionModel().addListSelectionListener(
                    new ListSelectionListener() {
                        @Override
                        public void valueChanged(ListSelectionEvent e) {
                            if (!e.getValueIsAdjusting()) {
                                // get the current selected row
                                int i = table.getSelectedRow();
                                // if there is a selected row, update the text field
                                if(i >= 0) {
                                   countryTxt.setText((String) model
                                        .getValueAt(i, 0));
                                }
                            }
                        }
                    });
            button.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent arg0) {
                    // get the current selected row
                    int i = table.getSelectedRow();
                    // if there's no selection, but there are some rows,
                    // we'll just delete the first row
                    if(i < 0 && model.getRowCount() > 0) {
                       i = 0;
                    }
    
                    // if we have a valid row to delete, do the deletion
                    if(i >= 0) {
                        countryTxt.setText("");
                        model.removeRow(i);
                        table.revalidate();
                    }
                }
            });
            frame.add(countryTxt,BorderLayout.NORTH);
            frame.add(new JScrollPane(table), BorderLayout.CENTER);
            frame.add(button, BorderLayout.SOUTH);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.pack();
            frame.setVisible(true);
            frame.setLocationRelativeTo(null);
        }
    
        public void data() {
            model.addRow(new String[] { "USA", "New York", "First street" });
            model.addRow(new String[] { "Russia", "Moscow", "Second street" });
            model.addRow(new String[] { "Japan", "Osaka", "Osaka street" });
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    new App();
                }
            });
        }
    }