Löschen von Tabellendaten

Das Löschen von Daten einer Tabelle geschieht, im Gegensatz zu deren bloßem Ausblenden, nicht in der Tabelle selbst, sondern in deren Datenverwaltungsstruktur, dem sog. Modell1.

Tabelle und Modell

Die Verwaltung der Daten einer JTable erfolgt innerhalb eines sog. Modells (engl. model). Es stellt eine Struktur dar, die alle Möglichkeiten kapselt, die Tabellendaten zu erweitern, zu modifizieren, zu löschen, etc. Die JTable selbst, stellt lediglich die optische Aufarbeitung der Daten, die 'View' dar. Durch die Koppelung des Modells mit den dort registrierten Listenern, kann bei Änderungen jedoch gleichzeitig eine Aktualisierung der Tabellenansicht vorgenommen werden.
Java definiert als Modell die abstrakte Klasse AbstractTableModel, die das Interface TableModel implementiert, und davon abgeleitet, die Klasse DefaultTableModel. Je nach Bedarf können beide Klassen erweitert und deren Methoden überschrieben werden. In einfachen Fällen, wie dem folgenden Beispiel, ist dies jedoch nicht notwendig.

Das Beispiel

Es zeigt eine einfache Tabelle mit fünf Spalten und sechs Reihen, sowie ebenfalls sechs Buttons, die, neben dem Wiederherstellen des Tabelleninhaltes, die hier besprochenen Funktionen durch Aufruf jeweils einer gesonderten Methode ausführen.

private JTable createTable() {
        data = new Object[][] { { "Ardie", "SV 500", "500", "14", "1930" },
                { "Horex", "Regina Sport", "342", "20", "1952" }, { "BMW", "R75/5", "750", "50", "1969" },
                { "Nimbus", "A", "746", "10", "1919" }, { "Scott", "Modell 3S", "986", "48", "1934" },
                { "Triumph", "BDG 250 H", "250", "12", "1952" }, };
        header = new Object[] { "Marke", "Typ", "Hubraum", "PS", "Baujahr" };
        DefaultTableModel model = new DefaultTableModel(data, header);
        JTable table = new JTable(model);
        table.setAutoCreateRowSorter(true);
        table.setRowSelectionAllowed(true);
        table.setColumnSelectionAllowed(true);
        table.setCellSelectionEnabled(true);
        table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
        return table;
    }

Die Tabelle selbst wird der Übersichtlichkeit halber in einer gesonderten Methode createTable() erzeugt. Zunächst wird dort ein zweidimensionales Array mit String-Daten initialisiert [2-5]. Sie repräsentieren den Tabelleninhalt, wobei die inneren Arrays für jeweils eine Tabellenzeile stehen. Die Spaltentitel der Tabelle müssen in einem gesonderten Array abgelegt werden [6]. Sie dienen zur Identifizierung der Spalten.
Beide Arrays werden als Parameter dem Konstruktor bei der Bildung eines DefaultTableModel übergeben [7]. Der Konstruktor der Klasse ist sechsfach überladen; insbesondere können Daten und Header auch jeweils als Vector übergeben werden. Das dergestalt erzeugte Modell dient schließlich als Parameter bei der Erzeugung der JTable [8].
Um die Tabellenspalten durch Klick auf den Spaltenkopf sortieren zu können, wird der Methode setAutoCreateRowSorter() true übergeben [9]. Die folgenden drei Zeilen ermöglichen eine multiple Auswahl von Tabellenzeilen und -spalten, sowie die Selektion von einzelnen Zellen [10-12]. Durch Setzen des tabelleneigenen Selection-Modells auf MULTIPLE_INTERVAL_SELECTION werden durch Drücken von Shift, bzw. Cmd Mehrfachauswahlen ermöglicht [13].

Das Beispielprogramm zum Löschen von Tabellendaten

Daten löschen und ausblenden

Wie oben bereits angedeutet, muss zwischen dem Löschen und Ausblenden von Tabellendaten unterschieden werden. Beim Löschen werden Daten aus dem Tabellenmodell vollständig entfernt, während sie beim Ausblenden lediglich in der Tabellenansicht unsichtbar gemacht werden, im Modell jedoch erhalten bleiben. Zur Überprüfung des jeweiligen Aktions-Ergebnisses dient hier die Methode printModel(). Sie wird am Ende jeder Methode aufgerufen und gibt alle Werte des Modells auf der Konsole aus, indem sie mittels getRowCount() die Zeilenanzahl des Modells und durch getColumnCount() die Zahl der Spalten ermittelt. Die Zeilen und Spalten werden innerhalb zweier verschachtelter Schleifen durchlaufen und die Werte über getValueAt() geholt und anschließend ausgegeben.

private void printModel(TableModel model) {
    for (int i = 0; i < model.getRowCount(); i++) {
        for (int j = 0; j < model.getColumnCount(); j++) {
            System.out.print(model.getValueAt(i, j) + "\t\t");
        }
        System.out.println();
    }
    System.out.println();
}

Löschen von Tabellenzellen

Das Löschen eines einzelnen Zelleninhaltes kann dadurch ermöglicht werden, dass die Tabelleneigenschaft cellSelectionEnabled, wie hier geschehen, auf true gesetzt wird. Zur Ermittlung eines einzelnen Zelleninhaltes kann dann durch die JTable-Methoden getSelectedRow() und getSelectedColumn() am Schnittpunkt der selektierten Zeile und Spalte die Zelle ermittelt werden. Durch die Modellmethode setValueAt() kann dann der Wert auf null gesetzt werden. Durch Verwenden dieser Methode wird sichergestellt, dass gleichzeitig alle in Frage kommenden, intern verwendeten EventListener benachrichtigt werden und somit auch die Tabellenview entsprechend geändert wird.

Was geschieht jedoch, wenn die Tabelleneinträge umsortiert wurden, etwa durch Klick auf den Spaltenkopf und somit View und Modell nicht mehr übereinstimmen? Für diesen Fall wird die Ermittlung der Reihen- und Spaltenposition über die Methoden convertRowIndexToModel() bzw. convertColumnIndexToModel() ausgeführt. Diese, in JTable definierten Methoden konvertieren die Indices entsprechend. Beide Methoden haben zudem mit convertRowIndexToView() und convertColumnIndexToView() auch Entsprechungen für ein umgekehrtes Vorgehen.

private void deleteCell(JTable table) {
    TableModel model = table.getModel();
    int selRow = table.getSelectedRow();
    int row = -1, col = -1;
    if (selRow > -1) {
        row = table.convertRowIndexToModel(table.getSelectedRow());
        col = table.convertColumnIndexToModel(table.getSelectedColumn());
        if (col > -1 && row > -1)
            model.setValueAt(null, row, col);
    }
    printModel(model);
}

Das Löschen mehrerer einzelner Zellen muss etwas genauer betrachtet werden. Java ermöglicht es in Swing nicht, wahlfrei eine beliebige Zahl einzelner Zellen zu selektieren. Es werden immer alle an Zeilen-Spalten-Schnittpunkten liegenden Zellen mitausgewählt. Dies wird relevant, wenn die Auswahl mehrerer Zellen in verschiedenen Reihen und Spalten liegen.

Die Auswahl von zwei Zellen führt zur Markierung von vier Zellen

Die Abbildung zeigt das Tabellenbeispiel, bei dem lediglich 'Regina Sport' und '1934' ausgewählt wurden, jedoch vier Zellen mit den zusätzlichen Inhalten 'Modell 3S' und '1952' als selektiert gekennzeichnet sind.

Das Vorgehen beim Löschen dieses, als ausgewählt gekennzeichneten Inhalts entspricht prinzipiell demjenigem beim Löschen einer Einzelzelle mit der Ausnahme, dass die ausgewählten Reihen und Spalten in Schleifen durchlaufen und dabei alle selektierten Zellen ermittelt werden. Deren Werte sind dann wieder auf null zu setzen.

private void deleteCells(JTable table) {
    TableModel model = table.getModel();
    for (int i = 0; i < table.getRowCount(); i++) {
        for (int j = 0; j < table.getColumnCount(); j++) {
            if (table.isCellSelected(i, j)) {
                model.setValueAt(null, table.convertRowIndexToModel(i), table.convertColumnIndexToModel(j));
            }
        }
    }
    printModel(model);
}

Löschen von Zeilen

Für das Löschen von einer oder mehrerer Zeilen werden zunächst die Indices der selektierten Reihen über die JTable-Methode getSelectedRows() in einem Array gespeichert [2]. In einer Schleife wird die Gesamtzahl aller Tabellenzeilen durchlaufen, und die markierten Zeilen werden aus dem Modell entfernt [4-7]. Hierzu stellt DefaultTableModel die Methode removeRow() bereit. Sie benötigt den Index der jeweiligen Zeile. Bei jedem Löschvorgang verringert sich jedoch die Anzahl der vorhandenen Reihen und damit die Anzahl der Schleifendurchläufe um eins. Der Wert der Zählvariablen muss somit vom Index der zu entfernenden Reihe subtrahiert werden [5], um eine ArrayIndexOutOfBoundsException zu vermeiden. Der ganze Vorgang funktioniert übrigens auch, wenn bei der Konfiguration der Tabelle mittels table.setCellSelectionEnabled(true) statt einer Reihe nur eine einzelne Zelle ausgewählt wurde.

private void deleteRowsFromModel(JTable table) {
    int[] rows = table.getSelectedRows();
    DefaultTableModel model = (DefaultTableModel) table.getModel();
    for (int i = 0; i < rows.length; i++) {
        rows[i] = rows[i] - i;
        model.removeRow(table.convertRowIndexToModel(rows[i]));
    }
    printModel(model);
}

Ausblenden von Zeilen

Das Ausblenden markierter Zeilen gestaltet sich deutlich komplexer als das bisher Gezeigte. Im Beispiel wird es über einen RowFilter vorgenommen. Laut API-Doc dient er zum Ausschließen von nicht anzuzeigenden Modell-Inhalten und muss bei einem RowSorter angemeldet werden [23]. Der Filter filtert auf Modell-Inhalte und kann nicht direkt auf den Zeilenindex angewandt werden. Somit muss zunächst der Inhalt selektierter Zeilen ermittelt und anschließend auf relevante Inhalte hin gefiltert werden. Im vorliegenden Beispiel geschieht dies, indem die Einträge der jeweils zweiten Spalten (Typ) der selektierten Reihen in einem Vector abgelegt werden [10]. Sie werden innerhalb eines Schleifendurchlaufs aller ausgewählten Zeilen ermittelt [7].
Beim erzeugten RowFilter-Objekt muss dessen Methode include() überschrieben werden [14]. Sie erhält als Argument ein RowFilter.Entry-Objekt, das mit den Einträgen der auszufilternden Reihen verglichen wird [16]. Wird der Eintrag gefunden, wird false zurückgegeben und die entsprechende Zeile wird ausgeblendet.

private void setRowsInvisible(JTable table) {
        int[] selRows = table.getSelectedRows();
        DefaultTableModel model = (DefaultTableModel) table.getModel();
        Vector<Vector> modelContent = model.getDataVector();
        Vector<Object> selContent = new Vector<Object>();
        for (int i = 0; i < selRows.length; i++) {
            Object o = modelContent.get(selRows[i]).get(1);
            if (o != null) {
                String s = o.toString();
                selContent.add(s);
            }
        }
        RowFilter<Object, Object> filter = new RowFilter<Object, Object>() {
            public boolean include(Entry<? extends Object, ? extends Object> entry) {
                for (int i = entry.getValueCount() - 1; i >= 0; i--) {
                    if (selContent.contains(entry.getStringValue(i))) {
                        return false;
                    }
                }
                return true;
            }
        };
        ((DefaultRowSorter<?, ?>) table.getRowSorter()).setRowFilter(filter);
        printModel(table.getModel());
    }

Löschen von Spalten

Um Spalten aus einem TableModel zu entfernen, bietet es sich an, ein neues Modell durch partielles Kopieren zu erzeugen und anschließend der Tabelle zu übergeben. Die Methode deleteColsFromModel() demonstriert hierzu ein mögliches Vorgehen.

Das Löschen geschieht hier dadurch, dass zunächst die Indices der zu löschenden Spalten ermittelt [6] und in eine List konvertiert werden [7]. Dieser Schritt ist sinnvoll, da auf diese Weise später durch contains() geprüft werden kann, ob sich ein Index in dieser Liste befindet [15]. Es werden dann drei Vectoren gebildet: ein Vector mit Vectoren zur Aufnahme der verbleibenden Modelldaten [8], ein Vector zur Aufnahme der Daten einer Tabellenreihe [9] und ein Vector zum Speichern des neuen Tabellenheaders [10]. Das bisherige Modell wird dann durchlaufen und die Daten Zeile für Zeile im Zeilenvector abgelegt[18]. Die Daten der zu entfernenden Spalte werden über die Indices identifiziert und das Ablegen der entsprechenden Werte dabei übersprungen [15-17].
Der Zeilenvector, rowVector, wird schließlich nach Durchlauf jeder Zeile dem Gesamtvector, dataVector, hinzugefügt [23]. Ein weiterer Vector, colIdent, speichert die verbleibenden Spaltennamen, die beim Durchlaufen der ersten Zeile durch die Modellmethode getColumnName() ermittelt werden [20].
Das neue Modell wird schließlich auf die bekannte Art erzeugt [25] und der Tabelle übergeben [26].

private void deleteColsFromModel(JTable table) {
    DefaultTableModel model = (DefaultTableModel) table.getModel();
    printModel(model);
    int rows = model.getRowCount();
    int cols = table.getColumnCount();
    int[] selCols = table.getSelectedColumns();
    List<Integer> selList = Arrays.stream(selCols).boxed().toList();
    Vector<Vector<Object>> dataVector = new Vector<Vector<Object>>();
    Vector<Object> rowVector;
    Vector<Object> colIdent = new Vector<Object>();

    for (int i = 0; i < rows; i++) {
        rowVector = new Vector<Object>();
        for (int j = 0; j < cols; j++) {
            if (selList.contains(j)) {
                continue;
            }
            rowVector.add(model.getValueAt(i, table.convertColumnIndexToModel(j)));
            if (i == 0) {
                colIdent.add(model.getColumnName(table.convertColumnIndexToModel(j)));
            }
        }
        dataVector.add(rowVector);
    }
    DefaultTableModel newModel = new DefaultTableModel(dataVector, colIdent);
    table.setModel(newModel);
    printModel(newModel);
}

Ausblenden von Spalten

Zum Ausblenden von Spalten werden deren Indices mittels getSelectedColumns() in einem Array gespeichert [2]. Innerhalb einer Schleife kann dann die Tabellenmethode removeColumn() aufgerufen werden. Sie muss ein Objekt vom Typ TableColumn als Argument übergeben bekommen. Dies erhält man, indem getColumn() mit dem Headernamen aufgerufen wird. Dieser wiederum wird durch getColumnName() ermittelt, indem die Methode den Spaltenindex als Parameter nutzt.

private void setColsInvisible(JTable table) {
    int[] cols = table.getSelectedColumns();
    for (int i = cols.length - 1; i > -1; i--) {
        table.removeColumn(table.getColumn(table.getColumnName(cols[i])));
    }
    printModel(table.getModel());
}

Vollständiger Quelltext

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Vector;

import javax.swing.DefaultRowSorter;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.RowFilter;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;

public class DeleteMultipleTableValues {

    ArrayList<int[]> selList = new ArrayList<int[]>();
    Object[][] data;
    Object[] header;

    public DeleteMultipleTableValues() {
        JFrame frame = new JFrame();
        initGui(frame);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setTitle("Tabellen-Werte l\u00f6schen");
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private void initGui(JFrame frame) {
        frame.setLayout(new BorderLayout());
        JTable table = createTable();
        JScrollPane sp = new JScrollPane(table);
        sp.setPreferredSize(new Dimension(sp.getPreferredSize().width, 150));
        frame.add(sp, BorderLayout.CENTER);
        JButton cellDelButt = new JButton("Zelle l&#246;schen");
        cellDelButt.addActionListener(e -> deleteCell(table));
        JButton modelRowDelButt = new JButton("Reihen l&#246;schen");
        modelRowDelButt.addActionListener(e -> deleteRowsFromModel(table));
        JButton rowDelButt = new JButton("Reihen ausblenden");
        rowDelButt.addActionListener(e -> setRowsInvisible(table));
        JButton modelColDelButt = new JButton("Spalten l&#246;schen");
        modelColDelButt.addActionListener(e -> deleteColsFromModel(table));
        JButton colDelButt = new JButton("Spalten ausblenden");
        colDelButt.addActionListener(e -> setColsInvisible(table));
        JButton resetButt = new JButton("Wiederherstellen");
        resetButt.addActionListener(e -> reset(table));
        JPanel buttonPanel = new JPanel();
        buttonPanel.setLayout(new GridLayout(2, 3));
        buttonPanel.add(cellDelButt);
        buttonPanel.add(rowDelButt);
        buttonPanel.add(modelRowDelButt);
        buttonPanel.add(resetButt);
        buttonPanel.add(colDelButt);
        buttonPanel.add(modelColDelButt);
        frame.add(buttonPanel, BorderLayout.SOUTH);
    }

    private JTable createTable() {
        data = new Object[][] { { "Ardie", "SV 500", "500", "14", "1930" },
                { "Horex", "Regina Sport", "342", "20", "1952" }, { "BMW", "R75/5", "750", "50", "1969" },
                { "Nimbus", "A", "746", "10", "1919" }, { "Scott", "Modell 3S", "986", "48", "1934" },
                { "Triumph", "BDG 250 H", "250", "12", "1952" }, };
        header = new Object[] { "Marke", "Typ", "Hubraum", "PS", "Baujahr" };
        DefaultTableModel model = new DefaultTableModel(data, header);
        JTable table = new JTable(model);
        table.setAutoCreateRowSorter(true);
        table.setRowSelectionAllowed(true);
        table.setColumnSelectionAllowed(true);
        table.setCellSelectionEnabled(true);
        table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
        return table;
    }

    private void deleteCell(JTable table) {
        TableModel model = table.getModel();
        int selRow = table.getSelectedRow();
        int row = -1, col = -1;
        if (selRow > -1) {
            row = table.convertRowIndexToModel(table.getSelectedRow());
            col = table.convertColumnIndexToModel(table.getSelectedColumn());
            if (col > -1 && row > -1)
                model.setValueAt(null, row, col);
        }
        printModel(model);
    }

    // unused
    private void deleteCells(JTable table) {
        TableModel model = table.getModel();
        for (int i = 0; i < table.getRowCount(); i++) {
            for (int j = 0; j < table.getColumnCount(); j++) {
                if (table.isCellSelected(i, j)) {
                    model.setValueAt(null, table.convertRowIndexToModel(i), table.convertColumnIndexToModel(j));
                }
            }
        }
        printModel(model);
    }

    private void setRowsInvisible(JTable table) {
        int[] selRows = table.getSelectedRows();
        DefaultTableModel model = (DefaultTableModel) table.getModel();
        Vector<Vector> modelContent = model.getDataVector();
        Vector<Object> selContent = new Vector<Object>();
        for (int i = 0; i < selRows.length; i++) {
            Object o = modelContent.get(selRows[i]).get(1);
            if (o != null) {
                String s = o.toString();
                selContent.add(s);
            }
        }
        RowFilter<Object, Object> filter = new RowFilter<Object, Object>() {
            public boolean include(Entry<? extends Object, ? extends Object> entry) {
                for (int i = entry.getValueCount() - 1; i >= 0; i--) {
                    if (selContent.contains(entry.getStringValue(i))) {
                        return false;
                    }
                }
                return true;
            }
        };
        ((DefaultRowSorter<?, ?>) table.getRowSorter()).setRowFilter(filter);
        printModel(table.getModel());
    }

    private void deleteRowsFromModel(JTable table) {
        int[] rows = table.getSelectedRows();
        DefaultTableModel model = (DefaultTableModel) table.getModel();
        for (int i = 0; i < rows.length; i++) {
            rows[i] = rows[i] - i;
            model.removeRow(table.convertRowIndexToModel(rows[i]));
        }
        printModel(model);
    }

    private void deleteColsFromModel(JTable table) {
        DefaultTableModel model = (DefaultTableModel) table.getModel();
        printModel(model);
        int rows = model.getRowCount();
        // Spaltenzahl der Tabelle, da Spalten ausgeblendet sein k&#246;nnen
        int cols = table.getColumnCount();
        int[] selCols = table.getSelectedColumns();
        List<Integer> selList = Arrays.stream(selCols).boxed().toList();
        Vector<Vector<Object>> dataVector = new Vector<Vector<Object>>();
        Vector<Object> rowVector;
        Vector<Object> colIdent = new Vector<Object>();

        for (int i = 0; i < rows; i++) {
            rowVector = new Vector<Object>();
            for (int j = 0; j < cols; j++) {
                if (selList.contains(j)) {
                    continue;
                }
                rowVector.add(model.getValueAt(i, table.convertColumnIndexToModel(j)));
                if (i == 0) {
                    colIdent.add(model.getColumnName(table.convertColumnIndexToModel(j)));
                }
            }
            dataVector.add(rowVector);
        }
        DefaultTableModel newModel = new DefaultTableModel(dataVector, colIdent);
        table.setModel(newModel);
        printModel(newModel);
    }

    private void setColsInvisible(JTable table) {
        System.out.println("deleteCols() called");
        int[] cols = table.getSelectedColumns();
        for (int i = cols.length - 1; i > -1; i--) {
            table.removeColumn(table.getColumn(table.getColumnName(cols[i])));
        }
        printModel(table.getModel());
    }

    private void reset(JTable table) {
        DefaultTableModel model = new DefaultTableModel();
        model.setDataVector(data, header);
        table.setModel(model);
    }

    private void printModel(TableModel model) {
        for (int i = 0; i < model.getRowCount(); i++) {
            for (int j = 0; j < model.getColumnCount(); j++) {
                System.out.print(model.getValueAt(i, j) + "\t\t");
            }
            System.out.println();
        }
        System.out.println();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new DeleteMultipleTableValues());
    }
}

1) Im Artikel wird innerhalb des Fließtextes die deutsche Schreibweise 'Modell' für das Interface TableModel oder die es implementierenden Klassen genutzt, sofern diese nicht im Sinne von Eigennamen verwendet werden. In Quelltexten und bei expliziter Nennung der Java-Bezeichner wird die Originalschreibweise beibehalten.

Wenn Ihnen javabeginners.de gefällt, freue ich mich über eine Spende an diese gemeinnützigen Organisationen.