JTable reicht manchmal den Klick in die Tabelle nicht weiter

Ich hab in einer komplexeren Anwendung das seltsame Problem, dass manchmal der Klick in eine Tabelle dergestalt nicht weitergereicht wird, dass zwar die Selektion dieser Zeile in der Tabelle selbst angezeigt wird, aber die Daten aus dieser Zeile nicht dargestellt werden.

Nach längeren versuchen mit synchronized (da ich erst dachte, sich zeitlich überschneidende Aufrufe von mouseClicked() wären Schuld) und Analysen dessen, was ich nach dem Tabellenklick anstelle, bin ich dann darauf gekommen, dass das Problem in tieferen Schichten liegen muss.

Hier ein Testprogramm:


import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.TableColumn;

public class TableClickNotPassed {

    static class TableEntry {
        private String code;
        private String contents;

        public TableEntry(String code, String contents) {
            this.code = code;
            this.contents = contents;
        }

        public String getCode() {
            return code;
        }

        public String getContents() {
            return contents;
        }
    }

    public class MyTableModel extends AbstractTableModel {

        private static final long serialVersionUID = -1669485540341234132L;

        private List<TableEntry> entryList;

        public MyTableModel() {
            super();
            entryList = new ArrayList<TableEntry>();
        }

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

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

        @Override
        public Object getValueAt(int row, int column) {
            String value = "";
            switch (column) {
                case 0:
                    value = entryList.get(row).getCode();
                    break;
                case 1:
                    value = entryList.get(row).getContents();
                    break;
            }
            return value;
        }

        public void clear() {
            entryList.clear();
        }

        public void addEntry(TableEntry entry) {
            entryList.add(entry);
        }
    }

    private JTable myTable;
    private JFrame frame;
    private JTextField textField;
    private List<TableEntry> data;

    public TableClickNotPassed() {
        data = createTestData();

        frame = new JFrame();
        frame.setLayout(new BorderLayout());

        textField = new JTextField();
        frame.add(textField, BorderLayout.NORTH);

        MyTableModel tableModel = new MyTableModel();
        fillTableModel(tableModel, data);
        myTable = new JTable(tableModel, createTableColumnModel());
        myTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        myTable.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent event) {
                int row = myTable.rowAtPoint(event.getPoint());
                System.out.println("mouseClicked: " + row);
                if (row > -1) {
                    textField.setText(data.get(row).getContents());
                }
            }
        });

        JScrollPane scrollPane = new JScrollPane(myTable);
        scrollPane.setPreferredSize(new Dimension(400, 300));
        frame.add(scrollPane, BorderLayout.CENTER);

        frame.setLocation(100, 100);
        frame.pack();
    }

    private static List<TableEntry> createTestData() {
        List<TableEntry> entries = new ArrayList<>();
        entries.add(new TableEntry("001", "Aaaaaaaaaaaaaaaaaaaaa"));
        entries.add(new TableEntry("002", "Bbbbbbbbbbbbbbbbbbbbb"));
        return entries;
    }

    private static DefaultTableColumnModel createTableColumnModel() {
        DefaultTableColumnModel columnModel1 = new DefaultTableColumnModel();

        TableColumn col1 = new TableColumn(0, 50);
        col1.setHeaderValue("Code");
        col1.setMaxWidth(50);
        columnModel1.addColumn(col1);
        TableColumn col2 = new TableColumn(1, 350);
        col2.setHeaderValue("Beschreibung");
        columnModel1.addColumn(col2);

        return columnModel1;
    }

    private static void fillTableModel(MyTableModel tableModel,
            List<TableEntry> entries) {
        for (TableEntry entry : entries) {
            tableModel.addEntry(entry);
        }
        tableModel.fireTableDataChanged();
    }

    public void setVisible(boolean visble) {
        frame.setVisible(visble);
    }

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

}

Normalerweise wird der unter Beschreibung in der Tabellenzeile angezeigte Text im Textfeld oben angezeigt. Klickt man lange und schnell genug zwischen den Tabellenzeilen hin und her, kann es vorkommen, dass z.B. die zweite Zeile Selektiert wird und im Textfeld noch der Inhalt der ersten Zeile angezeigt wird.

Die Testausgabe auf der Konsole endet auch entsprechend mit mouseClicked: 0, was bedeutet, dass mouseClicked() in diesem Fall nicht aufgerufen wurde.

Zur Verdeutlichung ein Bild:

schnell muss man da nicht sein, glaube ich, und eine andere Spezialität von dir kommt quasi zum Tragen: Drag & Drop :wink:

wenn du die Maus in einer Zeile drückst, gedrückt hältst und über einer anderen Zeile loslässt, dann ist das kein Klick-Ereignis, wohl aber eine Markierung,

die Markierung auch dann wenn du mit der Maus gedrückt über die andere Zeile kommst und weiter ganz raus gehst,
irgendwo im Nirvana, selbst außerhalb der GUI loslässt, da kann es ja kaum ein Klick-Ereignis geben

hier bei JTable ist es doch naheliegend, genau auf die ListSelection zu achten, darauf ein Listener,
dann hast du auch gleich Tastatursteuerung, programmatisches Setzen einer Selektion usw.


edit: bei Test-JFrames bitte immer setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setzen, sonst läuft das Programm nach X-Schließen der GUI weiter!

Am Code ist nichts offensichtliches falsch. Mit hoher (für mich, nach einem kurzen Test, an Sicherheit grenzender) Wahrscheinlichkeit hast du die Maus bei deinem “schnell hin und her Klicken” zwischen dem Drücken der Maustaste und dem Loslassen ein winziges Stück bewegt - also keinen Klick gemacht, sondern Pressed-Dragged-Released. Vielleicht sollte der Listener nicht auf Klicks hören, sondern auf Releases…

EDIT: Zu lahm… -_-

Warum nimmst du nicht gleich einen Selection Listener?

Das war genau der richtige Hinweis.

So funktioniert es im Beispiel mit dem SelectionListener:


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

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.TableColumn;

public class TableClickNotPassedRepaired {

    static class TableEntry {
        private String code;
        private String contents;

        public TableEntry(String code, String contents) {
            this.code = code;
            this.contents = contents;
        }

        public String getCode() {
            return code;
        }

        public String getContents() {
            return contents;
        }
    }

    public class MyTableModel extends AbstractTableModel {

        private static final long serialVersionUID = -1669485540341234132L;

        private List<TableEntry> entryList;

        public MyTableModel() {
            super();
            entryList = new ArrayList<TableEntry>();
        }

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

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

        @Override
        public Object getValueAt(int row, int column) {
            String value = "";
            switch (column) {
                case 0:
                    value = entryList.get(row).getCode();
                    break;
                case 1:
                    value = entryList.get(row).getContents();
                    break;
            }
            return value;
        }

        public void clear() {
            entryList.clear();
        }

        public void addEntry(TableEntry entry) {
            entryList.add(entry);
        }
    }

    private JTable myTable;
    private JFrame frame;
    private JTextField textField;
    private List<TableEntry> data;

    public TableClickNotPassedRepaired() {
        data = createTestData();

        frame = new JFrame();
        frame.setLayout(new BorderLayout());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        textField = new JTextField();
        frame.add(textField, BorderLayout.NORTH);

        MyTableModel tableModel = new MyTableModel();
        fillTableModel(tableModel, data);
        myTable = new JTable(tableModel, createTableColumnModel());
        myTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        myTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            @Override
            public void valueChanged(ListSelectionEvent e) {
                int row = myTable.getSelectedRow(); // nur bei ListSelectionModel.SINGLE_SELECTION !
                System.out.println("selected row: " + row);
                if (row > -1) {
                    textField.setText(data.get(row).getContents());
                }
            }
        });

        JScrollPane scrollPane = new JScrollPane(myTable);
        scrollPane.setPreferredSize(new Dimension(400, 300));
        frame.add(scrollPane, BorderLayout.CENTER);

        frame.setLocation(100, 100);
        frame.pack();
    }

    private static List<TableEntry> createTestData() {
        List<TableEntry> entries = new ArrayList<>();
        entries.add(new TableEntry("001", "Aaaaaaaaaaaaaaaaaaaaa"));
        entries.add(new TableEntry("002", "Bbbbbbbbbbbbbbbbbbbbb"));
        return entries;
    }

    private static DefaultTableColumnModel createTableColumnModel() {
        DefaultTableColumnModel columnModel1 = new DefaultTableColumnModel();

        TableColumn col1 = new TableColumn(0, 50);
        col1.setHeaderValue("Code");
        col1.setMaxWidth(50);
        columnModel1.addColumn(col1);
        TableColumn col2 = new TableColumn(1, 350);
        col2.setHeaderValue("Beschreibung");
        columnModel1.addColumn(col2);

        return columnModel1;
    }

    private static void fillTableModel(MyTableModel tableModel,
            List<TableEntry> entries) {
        for (TableEntry entry : entries) {
            tableModel.addEntry(entry);
        }
        tableModel.fireTableDataChanged();
    }

    public void setVisible(boolean visble) {
        frame.setVisible(visble);
    }

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

}