13.02.07 Tabellen
Eine weitere wichtige, aber auch kompilzierte Komponente aus der Swing-Familie ist die JTable
. Dabei handelt es sich, wie der Name schon vermuten lässt, um eine Klasse (zumindest im Vordergrund), die Daten in einer Tabelle anordnet. In diesem Kapitel werden Sie lernen, wie man Tabellen nutzt und für die eigenen Zwecke anpasst.
Die Arbeitsweise der JTable
Auch die JTable
arbeitet mit dem zuvor beschriebenen Schema, Daten und Grafik voneinander zu trennen. Für die Daten steht dabei die Schnittstelle TableModel
zur Verfügung. Wer nicht alle Methoden neu implementieren möchte, kann das DefaulTableModel
verwenden, welches die Daten als Vektoren von Vektoren speichert, doch dazu später mehr.
Die erste Tabelle
Das einfachste ist eine Tabelle direkt über den Konstruktor zu befüllen. Dazu rufen Sie den Konstruktor JTable(Object[][] rowData, Object[] columnNames)
auf. In rowData
stehen dabei die Daten, columnNames
gibt die Spaltennamen an.
Die Tabelle kann aber nicht so einfach auf einen JFrame
gesetzt werden, Sie müssen sie vorher in ein JScrollPane
packen, da sonst die Spaltennamen nicht angezeigt werden. Hier ein einfaches Beispiel:
import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTable;
JFrame frame = new JFrame(); frame.setBounds(100,100,500,500); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); String[] r1 ={"Max", "Mustermann"}; String[] r2 = {"Erika", "Mustermann"}; String[] r3 ={"Franz-Xaver", "Gabler"}; String[][] data = {r1, r2, r3}; String[] names = {"Vorname","Nachname"}; JTable table = new JTable(data, names); JScrollPane pane = new JScrollPane(table); frame.add(pane); frame.setVisible(true);
Sollen die Daten erst im Laufe des Programmes hinzugefügt werden, muss das Datenmodel angesprochen werden. Wenn im Konstruktor kein Model expilzit angegeben wird, dann wird das DefaulTabelModel
verwendet. Daher muss der Wert von jTable.getModel()
gecastet werden.
Anschließend stehen unter anderen die Methoden addRow(Object[] row)
, addColumn(Object columnName)
und removeRow(int row)
zur Verfügung. Die Veränderungen werden automatisch an einen TableModelListener
gesendet, der dafür sorgt, dass die Neuerungen auch gezeichnet werden.
Daten editieren
Die Daten einer Tabelle können vom Benutzer editiert werden, die veränderten Werten werden dann auch sofort ins Model eingetragen, sofern das verwendete Model das unterstützt. Das DefaultTableModel
tut dies, bei Ihren eigenen Modellen müssen Sie es selbst implementieren.
Die Werte können dann so abgerufen werden:
jTable.getModel().getValueAt(int rowIndex, int columnIndex);
Es gibt zwar auch die Methode jTable.getValueAt(int row, int column)
, diese richtet sich aber nach der angezeigten Spaltenreihenfolge, nicht nach der ursprünglich im Model festgelegten. Die Spaltenreihenfolge kann vom Nutzer geändert werden, wenn Sie ihm dies nicht ausdrücklich durch
table.getTableHeader().setReorderingAllowed(false);
verbieten.
Erweiterte Zelleninhalte
Normerlerweise wird der Tabelleninhalt immer in reinem Text angegeben, doch manchmal ist es wünschenswert, eine bestimmte Spalte farblich hervorzuheben oder gar einen Button in die Zelle zu setzen. Dafür müssen Sie das Interface TableCellRenderer
implementieren. Über die Methode getTableCellRendererComponent(...)
holt sich die Tabelle, sobald eine bestimmte Zelle gezeichnet werden soll, die entsprechende Komponente. Wollen Sie einen Button haben, müssen Sie an dieser Stelle einen Button zurückgeben.
Die obige Methode bringt Ihnen folgende Parameter mit, mit denen Sie das Aussehen und den Inhalt Ihres Buttons beeinflussen können:
- JTable table: Die verwendete Tabelle. Damit können Sie Farben sowie das Modell oder Zeilengrößen ermitteln.
- Object value: Der Wert, der in der betroffenen Zelle stehen soll.
- boolean isSelected: Ob die aktuelle Zelle selektiert ist.
- hasFocus: Ob die Tabelle zurzeit fokussiert ist.
Neben diesen Parametern gibt es noch die Werte row
und column
, die angeben, um welche Zelle es sich im Model handelt. Folgender Code zeigt, wie Sie in eine Zelle einen Tooltip-Text einbauen können:
package de.jbb.renderer import java.awt.Component; import javax.swing.JLabel; import javax.swing.JTable; import javax.swing.table.TableCellRenderer; public class ToolTipRenderer extends JLabel implements TableCellRenderer public ToolTipRenderer(){ setOpaque(true); } @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if (isSelected) { setBackground(table.getSelectionBackground()); setForeground(table.getSelectionForeground()); } else { setBackground(table.getBackground()); setForeground(table.getForeground()); } setFont(table.getFont()); if(value!=null){ setText(value.toString()); } setToolTipText(getText()); return this; } }
Nun muss der entsprechende Renderer noch der Tabelle zugewiesen werden. Soll er für alle Spalten gelten, können Sie ihn wie folgt zuweisen:
jTable.setDefaultRenderer(String.class, new ToolTipRenderer());
Wenn er dagegen nur in einer bestimmen Spalte angezeigt werden soll, legen Sie das über eine TableColumn
fest:
jTable.getColumn(columnName).setCellRenderer(new ToolTipRenderer());
Der Parament columnName
kann ein beliebiges Objekt sein, doch damit die gefunden werden kann muss die Spalte mit dem selben Objekt erzeugt worden sein, bzw. mit einem Objekt, das mit dem Aufruf von equals(Object o)
wieder gefunden werden kann. Eine sichere Methode ist demnach das Verwenden von Strings, wenn Sie nicht equals(Object o)
überschreiben wollen.
Tabellenspalten sortieren
Nicht immer ist es möglich, die Einträge einer Tabelle in der richtigen Reihenfolge hinzuzufügen, davon abgesehen, kann der Benutzer auch eine andere Ansicht von der „richtigen“ Reihenfolge haben. Daher ist es sinnvoll, dass Ihre Tabellen einen TableRowSorter
verwenden, der dafür sorgt, dass die Zeilen richtig geordnet werden. Einen TableRowSorter
hinzuzufügen ist einfach:
TableRowSorter sorter = new TableRowSorter(); sorter.setModel(jTable.getModel()); jTable1.setRowSorter(sorter);
Jetzt kann der Benutzer durch einen Klick auf einen Spaltenkopf die Tabelle entsprechend ordnen. Wenn Sie nichts anderes definiert haben, werden die Zelleninhalte in Strings umgewandelt und nach dem Alphabet geordnet. Sie können aber eigene Comperator
-Instanzen hinzufügen. Wie Sie Comperator
-Klassen schreiben können Sie in Kapitel D) Objekte sortieren -Comparator und Comparable nachlesen. Hinzugefügt wird der Comperator
dann für jede Spalte einzeln:
sorter.setComperator(0, new IntegerComperator()); // imagined class; sorter.setComperator(4, new PointComperator()); // imagined class
Spalten, die Sie nicht extra definieren werden weiterhin als String behandelt.
Ein eigenes TableModel verwenden
Von Zeit zu Zeit kann es sinnvoll sein, ein eigenes TableModel
zu implementieren um zum Beispiel auch mit Objekten in der Tabelle hantieren zu können. Hierzu implementieren Sie einfach TableModel
aus dem Paket javax.swing.table. Die zu implementierenden Methoden werden im Folgenden erklärt.
Die Methoden getColumnCount()
und getRowCount()
geben die Anzahl der Spalten bzw. der Zeilen zurück. Über getColumnName(int columnIndex)
erfragen sich die Listener den Spaltennamen einer bestimmten Spalte. Diese Listener werden über addTableModelListener(TableModelListener listener)
von u. a. der JTable
angemeldet und mit removeTableModelListener(TableModelListener listener)
beizeiten wieder gelöscht. Eine wichtige Methode ist getColumnClass(int columnIndex)
, denn sie liefert dem Aufrufer die Klasse, deren Objekte in dieser Spalte vorkommen.
Dann gibt es noch die Methode setValueAt(int row, int col)
, über die der Aufrufer den Wert einzelner Zellen im Modell speichern kann. Dies passiert beispielsweise, wenn eine Zelle editiert wurde. Wenn Sie nicht wollen, dass die Zellen editiert werden können, setzen Sie den Rückgabewert der Methode isCellEditable(int row, int col)
auf false
. Damit die Daten angezeigt werden ruft die für die Grafik zuständige Methode die Methode getValueAt(int row, int col)
auf, die den Wert ensprechend zurückgibt.
Beim Implementieren dieser Methoden müssen Sie sich auch um den Aufruf der Listener kümmern, da sonst niemand etwas von den Veränderungen mitbekommt.
Damit haben Sie alle obligatorischen Methoden implementiert. Dennoch ist es sinnvoll, zumindest eine zusätzliche Methode addRow(...)
zu definieren, die ein Objekt Ihrer Wahl annimmt.
Auf der nächsten Seite finden Sie ein Beispiel für ein TableModel
, das für den Einsatz von Point
gedacht ist.