Löschen von Tabellendaten
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].
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 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öschen");
cellDelButt.addActionListener(e -> deleteCell(table));
JButton modelRowDelButt = new JButton("Reihen löschen");
modelRowDelButt.addActionListener(e -> deleteRowsFromModel(table));
JButton rowDelButt = new JButton("Reihen ausblenden");
rowDelButt.addActionListener(e -> setRowsInvisible(table));
JButton modelColDelButt = new JButton("Spalten lö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ö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.