Stéphane Bruckert
Stéphane Bruckert

Reputation: 22903

JTable update after row selection

Here is an SSCCE of a JTextField that auto-completes a JTable.

It works fine until I wanted to do something when a row is selected. The problem is that whenever a row in the JTable is selected, changing the JTextField text throws an exception:

Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: -1
    at java.util.Vector.elementAt(Vector.java:430)
    at javax.swing.table.DefaultTableModel.getValueAt(DefaultTableModel.java:632)
    at javax.swing.JTable.getValueAt(JTable.java:2681)
    at fr.ensicaen.si.client.AnimalAutoComplete$1.valueChanged(AnimalAutoComplete.java:73)
    at javax.swing.DefaultListSelectionModel.fireValueChanged(DefaultListSelectionModel.java:167)
    at javax.swing.DefaultListSelectionModel.fireValueChanged(DefaultListSelectionModel.java:147)
    at javax.swing.DefaultListSelectionModel.fireValueChanged(DefaultListSelectionModel.java:194)
    at javax.swing.DefaultListSelectionModel.changeSelection(DefaultListSelectionModel.java:388)
    at javax.swing.DefaultListSelectionModel.changeSelection(DefaultListSelectionModel.java:398)
    at javax.swing.DefaultListSelectionModel.removeSelectionIntervalImpl(DefaultListSelectionModel.java:559)
    at javax.swing.DefaultListSelectionModel.clearSelection(DefaultListSelectionModel.java:403)
    at javax.swing.JTable.clearSelection(JTable.java:2075)
    at fr.ensicaen.si.client.AnimalAutoComplete$AutoCompleteListener.fill(AnimalAutoComplete.java:97)
    at fr.ensicaen.si.client.AnimalAutoComplete$AutoCompleteListener.insertUpdate(AnimalAutoComplete.java:93)

In fact, selecting a row and do something works. But when the JTextField is modified, the ListSelectionListener is fired again (but shouldn't?) and I can't figure out why!

import java.awt.BorderLayout;
import java.awt.EventQueue;
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.JTextField;
import javax.swing.border.EmptyBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableModel;

import java.awt.GridLayout;

public class AnimalAutoComplete extends JFrame {

    private JPanel contentPane;
    private JScrollPane animalPane;
    private JTable animalTable;
    private JPanel panel;
    private JTextField animalField;

    /** Create the frame. */
    public AnimalAutoComplete() {
        /* Frame structure */
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 710, 471);
        contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        contentPane.setLayout(new BorderLayout(0, 0));
        setContentPane(contentPane);
        animalPane = new JScrollPane();
        contentPane.add(animalPane, BorderLayout.CENTER);
        panel = new JPanel();
        contentPane.add(panel, BorderLayout.NORTH);
        panel.setLayout(new GridLayout(1, 0, 0, 0));

        /* JTable model */
        DefaultTableModel animalModel = new DefaultTableModel();
        animalTable = new JTable(animalModel);
        animalPane.setViewportView(animalTable);
        animalModel.addColumn("Animal"); 

        /* Initially fills the JTable */
        List<String> animals = new ArrayList<String>();
        animals.add("Dog");
        animals.add("Cat");
        animals.add("Fish");

        for (String a : animals) {
            animalModel.addRow( new Object[] {a} );
        }

        /* Text fields */
        animalField = new JTextField();
        panel.add(animalField);
        animalField.setColumns(10);

        /* This listener updates the JTable depending on the JTextField */
        animalField.getDocument().addDocumentListener(new AutoCompleteListener(animalTable, animals, animalField));

        /* This listener will do something useful when an animal is selected*/
        animalTable.getSelectionModel().addListSelectionListener(
            new ListSelectionListener(){
                public void valueChanged(ListSelectionEvent event) {
                    System.out.println(animalTable.getValueAt(animalTable.getSelectedRow(), 0));
                }
            }
        );

    }

    private final class AutoCompleteListener implements DocumentListener {
        private final JTable animalTable;
        private final List<String> animals;
        private JTextField searchedAnimal;

        private AutoCompleteListener(JTable animalTable,
                List<String> animals, JTextField textAnimal) {
            this.animalTable= animalTable;
            this.animals = animals;
            this.searchedAnimal = textAnimal;
        }

        public void changedUpdate(DocumentEvent arg0) {fill();}
        public void insertUpdate(DocumentEvent arg0) {fill();}
        public void removeUpdate(DocumentEvent arg0) {fill();}

        public void fill() {
            animalTable.clearSelection();
            DefaultTableModel model = (DefaultTableModel) animalTable.getModel();
            model.setRowCount(0);
            for (String a : animals) {
                if (a.startsWith(this.searchedAnimal.getText())) {
                    model.addRow(new Object[] {a});
                }
            }
        }
    }

    public static void main(String[] args) {
        AnimalAutoComplete frame = new AnimalAutoComplete();
        frame.setVisible(true);
    }
}

Upvotes: 0

Views: 1717

Answers (1)

Sage
Sage

Reputation: 15418

The problem is that the selection event is fired twice when a table row(or col/cell) gets selected. First time with index -1 and the second time with correct index. So check with a condition if animalTable.getSelectedRow() is returning -1:

        public void valueChanged(ListSelectionEvent event) {
             int selRow = animalTable.getSelectedRow()
             if(selRow >= 0)
             {
                System.out.println(animalTable.getValueAt(selRow, 0));
                // or do other things
             }
         }

As @camickr has suggested below, you can also make use of event.getValueIsAdjusting() method: which returns true if the selection is still changing. Many list selection listeners are interested only in the final state of the selection and can ignore list selection events when this method returns true. In fact using this function is preferable against the one mentioned above as it makes the action code more specific.

Upvotes: 1

Related Questions