From 33613a85afc4b1481367fbe92a17ee59c240250b Mon Sep 17 00:00:00 2001
From: Sven Eisenhauer
+Eines der in anspruchsvollen Benutzeroberflächen am häufigsten
+gebrauchten Dialogelemente ist die Tabelle, also eine mehrzeilige,
+mehrspaltige Darstellung von Daten. Diese im AWT fehlende Komponente
+wird in Swing durch die Klasse JTable
+zur Verfügung gestellt. Mit ihrer Hilfe lassen sich unterschiedlichste
+Arten von textuellen oder grafischen Daten tabellarisch darstellen
+und editieren. Das Programm hat dabei weitreichende Möglichkeiten,
+die Tabelle zu konfigurieren, ihren Inhalt anzupassen und auf Benutzerereignisse
+zu reagieren.
+
+
+Die wichtigsten Konstruktoren von JTable
+sind:
+
+
+An den ersten Konstruktor werden die darzustellenden Daten in Form
+eines zweidimensionalen Arrays übergeben. Dessen erste Dimension
+enthält die Zeilen, die zweite die Spalten. Zur Darstellung in
+der Tabelle werden die Array-Elemente mit toString
+in Strings umgewandelt. Das zweite Argument enthält ein Array
+mit Strings, die als Spaltenköpfe angezeigt werden.
+
+
+Statt der Übergabe von Arrays kann auch der zweite Konstruktor
+verwendet und die Daten und Spaltenköpfe in einem Vector
+übergeben werden. In diesem Fall muss der Datenvektor rowData
+für jede Zeile einen Untervektor mit den Datenelementen dieser
+Zeile enthalten.
+
+
+Der dritte Konstruktor stellt die allgemeinste Möglichkeit dar,
+eine JTable
+zu konstruieren. Hierbei werden alle drei Modelle der Tabelle explizit
+an den Konstruktor übergeben. Das TableModel
+stellt dabei die Daten zur Verfügung, das TableColumnModel
+definiert die Spalten, und das ListSelectionModel
+ist für die Selektion von Tabellenelementen zuständig. Werden
+alle drei Modelle separat instanziert und übergeben, hat das
+Programm die volle Kontrolle über alle Aspekte der Tabellendarstellung
+und -verarbeitung.
+
+
+Eine einfache Tabelle läßt sich also sehr schnell erzeugen:
+
+
+
+
+
+
+ Titel
+ Inhalt
+ Suchen
+ Index
+ DOC
+ Handbuch der Java-Programmierung, 5. Auflage
+
+ <<
+ <
+ >
+ >>
+ API
+ Kapitel 38 - Swing: Komponenten II
+
+
+
+
+
+38.2 JTable
+
+
+
+
+
+
+
+
+38.2.1 Erzeugen von Tabellen
+
+
+
+
+
+
+
+
+
+
+public JTable(Object[][] rowData, Object[] columnNames)
+public JTable(Vector rowData, Vector columnNames)
+public JTable(TableModel dm, TableColumnModel cm, ListSelectionModel sm)
+
+
+
+javax.swing.JTable
+
+
+
+Listing 38.4: Eine einfache Tabelle
+
+
+
+
+
+001 /* Listing3804.java */
+002
+003 import java.awt.*;
+004 import java.awt.event.*;
+005 import javax.swing.*;
+006
+007 public class Listing3804
+008 extends JFrame
+009 implements TableData
+010 {
+011 public Listing3804()
+012 {
+013 super("JTable 1");
+014 addWindowListener(new WindowClosingAdapter(true));
+015 JTable table = new JTable(DATA, COLHEADS);
+016 Container cp = getContentPane();
+017 cp.add(new JLabel("Alte c\'t-Ausgaben:"), BorderLayout.NORTH);
+018 cp.add(new JScrollPane(table), BorderLayout.CENTER);
+019 }
+020
+021 public static void main(String[] args)
+022 {
+023 Listing3804 frame = new Listing3804();
+024 frame.setLocation(100, 100);
+025 frame.setSize(300, 200);
+026 frame.setVisible(true);
+027 }
+028 }
+
+
+Listing3804.java
+
+Die Ausgabe des Programms ist: +
+ +
+Abbildung 38.5: Eine einfache Tabelle
++
![]() |
+![]() |
+
+
+ +Wie in anderen großen Dialogelementen haben wir die JTable +vor der Übergabe an ihren GUI-Container in eine JScrollPane +verpackt. Neben dem Effekt, die Daten vertikal oder horizontal scrollen +zu können, hat das vor allem zur Folge, dass die Spaltenköpfe +angezeigt werden. Ohne JScrollPane +wären sie dagegen nicht sichtbar. |
+
+
|
+![]() |
+
+In Listing 38.4 wurden zwei +Konstanten DATA und COLHEADS +verwendet. Sie dienen als Beispieldaten für die Programme dieses +Abschnitts und wurden als Konstanten in dem Interface TableData +definiert: + + +
+
+
+
+001 /* TableData.java */
+002
+003 public interface TableData
+004 {
+005 public static final String[][] DATA = {
+006 {" 1/1987", "195", "Vergleichstest EGA-Karten"},
+007 {" 2/1987", "171", "Schneider PC: Bewährungsprobe"},
+008 {" 3/1987", "235", "Luxus-Textsyteme im Vergleich"},
+009 {" 4/1987", "195", "Turbo BASIC"},
+010 {" 5/1987", "211", "640-K-Grenze durchbrochen"},
+011 {" 6/1987", "211", "Expertensysteme"},
+012 {" 7/1987", "199", "IBM Model 30 im Detail"},
+013 {" 8/1987", "211", "PAK-68: Tuning für 68000er"},
+014 {" 9/1987", "215", "Desktop Publishing"},
+015 {"10/1987", "279", "2,5 MByte im ST"},
+016 {"11/1987", "279", "Transputer-Praxis"},
+017 {"12/1987", "271", "Preiswert mit 24 Nadeln"},
+018 {" 1/1988", "247", "Schnelle 386er"},
+019 {" 2/1988", "231", "Hayes-kompatible Modems"},
+020 {" 3/1988", "295", "TOS/GEM auf 68020"},
+021 {" 4/1988", "263", "Projekt Super-EGA"},
+022 {" 5/1988", "263", "Neuheiten auf der CeBIT 88"},
+023 {" 6/1988", "231", "9600-Baud-Modem am Postnetz"}
+024 };
+025
+026 public static final String[] COLHEADS = {
+027 "Ausgabe", "Seiten", "Titelthema"
+028 };
+029 }
+
+ |
++TableData.java | +
+Auf diese Weise können die Daten in den folgenden Beispielen +dieses Abschnitts einfach mit der Anweisung implements +TableData importiert und dem Programm zur Verfügung +gestellt werden. Diese - auf den ersten Blick - etwas ungewöhnliche +Verwendung eines Interfaces ist ein Standard-Idiom in Java und wurde +in Abschnitt 9.4.1 erläutert. + + + + +
+Eine JTable +läßt sich auf vielfältige Weise konfigurieren. Mit +setRowHeight +wird die Gesamthöhe einer Zeile festgelegt, alle Zeilen sind +dabei gleich hoch. Mit setRowMargin +wird der am oberen und unteren Rand jeder Zelle freibleibende Platz +bestimmt. Der für den Inhalt der Zelle verfügbare Platz +ergibt sich aus der Zellenhöhe minus oberem und unterem Rand. +Durch Aufruf von setIntercellSpacing +kann (zusammen mit dem vertikalen) auch der horizontale Rand der Zellenelemente +festgelegt werden: +
+
+
++public void setRowHeight(int newHeight) +public void setRowMargin(int rowMargin) +public void setIntercellSpacing(Dimension newSpacing) ++ + |
++javax.swing.JTable | +
+Standardmäßig werden die Zellen einer JTable +mit senkrechten und waagerechten Begrenzungslinien voneinander getrennt. +Mit setShowGrid +können beide Linienarten zugleich an- oder ausgeschaltet werden. +Sollen die horizontalen oder vertikalen Linien separat aktiviert oder +deaktiviert werden, können die Methoden setShowHorizontalLines +und setShowVerticalLines +verwendet werden: +
+
+
++public void setShowGrid(boolean b) +public void setShowHorizontalLines(boolean b) +public void setShowVerticalLines(boolean b) ++ + |
++javax.swing.JTable | +
+Das Verändern der Farben der Zellen ist in begrenzter Weise mit +folgenden Methoden möglich: +
+
+
++public void setGridColor(Color newColor) +public void setSelectionForeground(Color selectionForeground) +public void setSelectionBackground(Color selectionBackground) ++ + |
++javax.swing.JTable | +
+setGridColor +verändert die Farbe, in der die Gitterlinien angezeigt werden. +Mit setSelectionForeground +und setSelectionBackground +wird die Vorder- und Hintergrundfarbe des selektierten Bereichs festgelegt. + +
+Als letzte der Konfigurationsmethoden wollen wir uns setAutoResizeMode +ansehen: +
+
+
++public void setAutoResizeMode(int mode) ++ + |
++javax.swing.JTable | +
+Sie bestimmt das Verhalten der Tabelle, nachdem die Breite einer einzelnen +Spalte verändert wurde. Der dadurch freiwerdende oder zusätzlich +benötigte Platz kann nämlich auf unterschiedliche Weise +den übrigen Spalten zugeordnet werden. Der Parameter mode +kann folgende Werte annehmen: + +
+
| Modus | +Bedeutung |
| AUTO_RESIZE_OFF | +Es erfolgt keine automatische Größenanpassung +der übrigen Spalten. Wurde die Tabelle in JScrollPane +verpackt, bekommt sie nötigenfalls einen horizontalen Schieberegler. |
| AUTO_RESIZE_LAST_COLUMN | +Die letzte Spalte wird zum Größenausgleich +verwendet. Dadurch reduziert sich der Platz für die letzte Spalte, +wenn eine andere Spalte vergrößert wird, und er erhöht +sich, wenn sie verkleinert wird. |
| AUTO_RESIZE_NEXT_COLUMN | +Die rechts neben der modifizierten Spalte +liegende Spalte wird zum Größenausgleich verwendet. |
| AUTO_RESIZE_SUBSEQUENT_COLUMNS | +Die Größenänderung wird +gleichmäßig auf alle nachfolgenden Spalten verteilt. |
| AUTO_RESIZE_ALL_COLUMNS | +Die Größenänderung wird +auf alle Spalten der Tabelle verteilt. |
+Tabelle 38.2: Parameter für setAutoResizeMode
+ + + + ++Die Elemente einer JTable +können auf unterschiedliche Weise selektiert werden. Welche Möglichkeiten +der Selektion dem Anwender zur Verfügung gestellt werden, regeln +die folgenden Methoden: +
+
+
++public void setRowSelectionAllowed(boolean flag) +public void setColumnSelectionAllowed(boolean flag) +public void setSelectionMode(int selectionMode) +public void setCellSelectionEnabled(boolean flag) ++ + |
++javax.swing.JTable | +
+Soll zeilenweise selektiert werden, ist setRowSelectionAllowed +mit true +als Argument aufzurufen. Soll spaltenweise selektiert werden, ist +analog setColumnSelectionAllowed +aufzurufen. Durch Übergabe von false +können beide Selektionsarten ausgeschaltet werden und nur noch +einzelne Zellen selektiert werden. Standardmäßig kann zeilen-, +aber nicht spaltenweise selektiert werden. + +
+Mit setSelectionMode +wird festgelegt, ob ein einzelnes Element, ein zusammenhängender +Bereich oder mehrere Bereiche selektiert werden können. Hier +ist eine der in Abschnitt 37.3.1 +beschriebenen Konstanten SINGLE_SELECTION, +SINGLE_INTERVAL_SELECTION +oder MULTIPLE_INTERVAL_SELECTION +der Klasse ListSelectionModel +zu übergeben. Wird setCellSelectionEnabled +mit true +als Argument aufgerufen, können Zeilen und Spalten gleichzeitig +markiert und so zusammenhängende rechteckige Bereiche von Zellen +(einschließlich einer einzelnen) selektiert werden. + + + + +
+Um herauszufinden, welche Elemente selektiert wurden, können +folgende Methoden verwendet werden: +
+
+
++public int getSelectedRow() +public int getSelectedColumn() + +public int[] getSelectedRows() +public int[] getSelectedColumns() ++ + |
++javax.swing.JTable | +
+getSelectedRow +und getSelectedColumn +liefern die selektierte Zeile bzw. Spalte, wenn der Selektionsmodus +SINGLE_SELECTION +ist. Die erste Zeile und Spalte haben dabei jeweils den Index 0. Erlaubt +der aktuelle Selektionsmodus das Selektieren ganzer Zeilen oder Spalten, +impliziert das Ergebnis, dass alle Elemente dieser Zeile bzw. +Spalte selektiert sind. Ist einer der Mehrfachselektionsmodi aktiviert, +können mit getSelectedRows +und getSelectedColumns +Arrays mit allen selektierten Zeilen und Spalten beschafft werden. +
+
![]() |
+
+
+ +Falls keine Elemente selektiert sind, geben getSelectedRow +und getSelectedColumn +-1 und getSelectedRows +und getSelectedColumns +ein leeres Array zurück. |
+
+
|
+![]() |
+
+JTable +stellt auch Methoden zur Verfügung, mit denen die Selektion programmgesteuert +verändert werden kann: +
+
+
++public void selectAll() +public void clearSelection() + +public void setRowSelectionInterval(int index0, int index1) +public void addRowSelectionInterval(int index0, int index1) +public void removeRowSelectionInterval(int index0, int index1) + +public void setColumnSelectionInterval(int index0, int index1) +public void addColumnSelectionInterval(int index0, int index1) +public void removeColumnSelectionInterval(int index0, int index1) ++ + |
++javax.swing.JTable | +
+Mit selectAll +kann die komplette Tabelle markiert werden, mit clearSelection +wird die Selektion entfernt. Mit setRowSelectionInterval +kann ein zusammenhängender Bereich von Zeilen markiert werden. +Mit addRowSelectionInterval +wird ein solcher zur aktuellen Selektion hinzugefügt und mit +removeRowSelectionInterval +daraus entfernt. Für die Selektion von Spalten stehen die analogen +Methoden setColumnSelectionInterval, +addColumnSelectionInterval +und removeColumnSelectionInterval +zur Verfügung. +
+
![]() |
+![]() |
+
+
+ +Damit die beschriebenen Methoden korrekt funktionieren, sollte ihr +Aufruf in Einklang mit den aktuell gewählten Selektionsmodi stehen. +Im RC1 des JDK 1.3 gab es beispielsweise Probleme, wenn selectAll +auf einer Tabelle aufgerufen wurde, die nur Einfachselektion erlaubte. +Nach dem Aufruf wurde zwar (korrekterweise) keine Selektion mehr angezeigt, +die Methoden zur Abfrage der selektierten Elemente verhielten sich +aber dennoch so, als wären alle Elemente selektiert. Bei Verwendung +von addRowSelectionInterval +fiel dagegen auf, dass die Methode nur dann korrekt funktionierte, +wenn der Selektionsmodus MULTIPLE_INTERVAL_SELECTION +aktiviert war. |
+
+
|
+![]() |
+
+Unabhängig von der aktuellen Selektion kann natürlich auch +auf den Inhalt der Tabelle zugegriffen werden: +
+
+
++public int getRowCount() +public int getColumnCount() + +public Object getValueAt(int row, int column) +public void setValueAt(Object aValue, int row, int column) ++ + |
++javax.swing.JTable | +
+getRowCount +und getColumnCount +liefern die aktuelle Zeilen- bzw. Spaltenzahl der Tabelle. Mit getValueAt +kann auf das Element an der Position (row, column) zugegriffen +werden. Beide Indices beginnen bei 0, ein Zugriff außerhalb +der Grenzen wird mit einer ArrayIndexOutOfBoundsException +quittiert. Mit setValueAt +kann ein Zellenelement sogar verändert werden. +
+
![]() |
+![]() |
+
+
+ +Bei der Verwendung der Methoden getValueAt +und setValueAt +ist es wichtig zu wissen, dass die angegebenen Zeilen- und Spaltenwerte +sich auf die aktuelle Ansicht der Tabelle beziehen, nicht auf ihr +Modell. Hat der Anwender beispielsweise die Spalten eins und zwei +vertauscht, würde ein Zugriff auf ein Element in Spalte eins +den Modellwert in Spalte zwei verändern und umgekehrt. Während +dieses Verhalten erwartungskonform ist, wenn der Wert durch den Anwender +editiert wird, würde es bei programmgesteuertem Aufruf zu einem +logischen Fehler kommen, denn das Programm hat natürlich zunächst +einmal keine Kenntnis davon, dass der Anwender die Spaltenreihenfolge +verändert hat. In aller Regel werden programmgesteuerte Zugriffe +auf einzelne Zellen daher nicht mit getValueAt +und setValueAt +ausgeführt, sondern an die gleichnamigen Methoden des TableModel +delegiert. |
+
+
|
+![]() |
+
+Nach einem Doppelklick auf eine Zelle kann der Anwender die in diesem +Element enthaltenen Daten verändern. JTable +besitzt einige Methoden, mit denen das Programm abfragen kann, ob +und in welcher Zelle die Tabelle gerade editiert wird: +
+
+
++public boolean isEditing() +public int getEditingRow() +public int getEditingColumn() ++ + |
++javax.swing.JTable | +
+isEditing +gibt genau dann true +zurück, wenn gerade ein Element der Tabelle geändert wird. +Mit getEditingRow +und getEditingColumn +kann das Programm herausfinden, welches Element betroffen ist. Wird +keine Zelle editiert, geben die Methoden -1 zurück. Zudem kann +das Programm durch Aufruf von editCellAt +selbst das Editieren eines Tabellenelements einleiten: +
+
+
++public boolean editCellAt(int row, int column) ++ + |
++javax.swing.JTable | +
+
![]() |
+
+
+ +Unabhängig von seinem bisherigen Typ wird der geänderte +Wert nach Abschluss des Änderungsvorgangs als String +in das Modell zurückgeschrieben. Wird - wie im letzten Beispiel +- ein Object-Array +als Modell verwendet, ist diese Typkonvertierung zwar korrekt, kann +aber bei der Weiterverarbeitung des Modells zu Überraschungen +führen. Besser ist es, Eingaben des Anwenders direkt nach der +Eingabe zu prüfen und vor der Speicherung in den passenden Typ +umzuwandeln. Wir werden im nächsten Abschnitt zeigen, wie man +das mit Hilfe eines eigenen Tabellenmodells erreichen kann. |
+
+
|
+![]() |
+
+Für einfache Anwendungen reicht es aus, mit den automatisch erzeugten +Tabellenmodellen zu arbeiten. Für Anwendungen mit komplexer strukturierten +Daten, oder solchen, die für ein Array zu umfangreich oder an +externe Quellen gebundenen sind, ist es dagegen sinnvoll, ein eigenes +Tabellenmodell zu implementieren. Dieses muss das Interface TableModel +aus dem Paket javax.swing.table +implementieren und bei der Instanzierung an den Konstruktor der JTable +übergeben. Wahlweise kann auch nach der Instanzierung auf das +Modell zugegriffen werden: +
+
+
++public void setModel(TableModel newModel) +public TableModel getModel() ++ + |
++javax.swing.JTable | +
+Das Interface TableModel +definiert folgende Methoden: +
+
+
++public int getRowCount() +public int getColumnCount() + +public String getColumnName(int columnIndex) +public Class getColumnClass(int columnIndex) + +public boolean isCellEditable(int rowIndex, int columnIndex) + +public Object getValueAt(int rowIndex, int columnIndex) +public void setValueAt(Object aValue, int rowIndex, int columnIndex) + +public void addTableModelListener(TableModelListener l) +public void removeTableModelListener(TableModelListener l) ++ + |
++javax.swing.table.TableModel | +
+Die meisten von ihnen sind Service-Methoden. Sie werden von JTable +aufgerufen, um Informationen zur Darstellung der Tabelle zu erhalten. +getRowCount +und getColumnCount +liefern die Anzahl der Zeilen und Spalten, getColumnName +die Spaltenüberschrift und getColumnClass +den Typ der Elemente einer Spalte. Mit isCellEditable +wird abgefragt, ob eine bestimmte Zelle editiert werden darf oder +nicht. Mit getValueAt +fragt die Tabelle beim Modell nach dem Wert einer bestimmten Zelle, +und mit setValueAt +wird ein geänderter Wert in das Modell zurückgeschrieben. + +
+Mit den Methoden addTableModelListener +und removeTableModelListener +kann ein TableModelListener +registriert bzw. deregistriert werden. Er wird über alle Änderungen +des Modells unterrichtet und damit insbesondere aufgerufen, wenn eine +Zeile oder Spalte eingefügt oder gelöscht wurde, wenn der +Inhalt einer Zelle modifiziert wurde oder wenn die Gesamtstruktur +des Modells sich geändert hat. Typischerweise registriert sich +die JTable +bei ihrem Modell, um auf Modelländerungen mit entsprechenden +Änderungen der Benutzeroberfläche reagieren zu können. + + + + +
+Als Beispiel wollen wir ein Modell konstruieren, das eine sehr große +Tabelle repräsentieren kann (z.B. mit 1000 mal 1000 Elementen), +von denen aber nur sehr wenige tatsächlich einen Wert enthalten +und alle anderen leer sind. Statt einer speicherintensiven Darstellung +mittels eines entsprechend dimensionierten Arrays sollen nur die tatsächlich +belegten Elemente gespeichert werden. Wir wollen dazu eine Hashtable +verwenden, deren Elemente die tatsächlich vorhandenen Werte sind. +Als Schlüssel verwenden wir eine String-Darstellung der Koordinaten +des Elements. Der Zugriff auf ein Element erfolgt dann, indem dessen +Koordinatenschlüssel in der Hashtable +gesucht und der zugehörige Wert zurückgegeben bzw. gespeichert +wird. + +
+Um das Tabellenmodell nicht von Grund auf neu entwickeln zu müssen, +leiten wir es aus der Klasse AbstractTableModel +des Pakets javax.swing.table +ab. Diese bietet für fast alle erforderlichen Methoden Standardimplementierungen +und stellt darüber hinaus einige nützliche Hilfsmethoden +zur Verfügung: + + +
+
+
+
+001 /* SparseTableModel.java */
+002
+003 import java.util.*;
+004 import javax.swing.*;
+005 import javax.swing.table.*;
+006
+007 public class SparseTableModel
+008 extends AbstractTableModel
+009 {
+010 private int size;
+011 private Hashtable data;
+012
+013 //Konstruktor
+014 public SparseTableModel(int size)
+015 {
+016 this.size = size;
+017 this.data = new Hashtable();
+018 }
+019
+020 //Methoden für das TableModel-Interface
+021 public int getRowCount()
+022 {
+023 return size;
+024 }
+025
+026 public int getColumnCount()
+027 {
+028 return size;
+029 }
+030
+031 public String getColumnName(int columnIndex)
+032 {
+033 return "C" + columnIndex;
+034 }
+035
+036 public Class getColumnClass(int columnIndex)
+037 {
+038 return String.class;
+039 }
+040
+041 public boolean isCellEditable(int rowIndex, int columnIndex)
+042 {
+043 return rowIndex < size && columnIndex < size;
+044 }
+045
+046 public Object getValueAt(int rowIndex, int columnIndex)
+047 {
+048 String key = "[" + rowIndex + "," + columnIndex + "]";
+049 String value = (String)data.get(key);
+050 return value == null ? "-" : value;
+051 }
+052
+053 public void setValueAt(Object aValue, int rowIndex, int columnIndex)
+054 {
+055 String key = "[" + rowIndex + "," + columnIndex + "]";
+056 String value = (String)aValue;
+057 if (value.length() <= 0) {
+058 data.remove(key);
+059 } else {
+060 data.put(key, value);
+061 }
+062 }
+063
+064 //Zusätzliche Methoden
+065 public void printData()
+066 {
+067 Enumeration e = data.keys();
+068 while (e.hasMoreElements()) {
+069 String key = (String)e.nextElement();
+070 System.out.println(
+071 "At " + key + ": " + (String)data.get(key)
+072 );
+073 }
+074 }
+075 }
+
+ |
++SparseTableModel.java | +
+Die Klasse wird durch Übergabe der Anzahl der Zeilen und Spalten +instanziert. getRowCount +und getColumnCount +liefern genau diesen Wert zurück. Als Spaltenname wird ein »C«, +gefolgt von der Nummer der Spalte angegeben. Alle Spalten sind vom +Typ String +und alle Zellen sind editierbar. Wird mit getValueAt +der Inhalt einer bestimmten Tabellenzelle abgefragt, so bildet die +Methode den Schlüssel aus Zeilen- und Spaltenindex und sucht +damit in der Hashtable +data. Falls ein Eintrag gefunden +wird, gibt getValueAt +diesen an den Aufrufer zurück, andernfalls wird nur ein Minuszeichen +geliefert. setValueAt +arbeitet analog. Auch hier wird zunächst der Schlüssel gebildet +und dann zusammen mit dem zugehörigen Wert in der Hashtable +gespeichert. Die Hilfemethode printData +dient dazu, alle vorhandenen Werte samt Koordinatenschlüsseln +auf der Konsole auszugeben. + +
+Mit Hilfe dieses Modells Tabellen zu bauen, die auch bei großen +Abmessungen noch effizient arbeiten, ist nicht mehr schwer. Das folgende +Programm zeigt das am Beispiel einer Tabelle mit einer Million Zellen. +Neben der Tabelle enthält es einen Button »Drucken«, +mit dem die aktuelle Belegung der Tabelle ausgegeben werden kann. + + +
+
+
+
+001 /* Listing3807.java */
+002
+003 import java.awt.*;
+004 import java.awt.event.*;
+005 import javax.swing.*;
+006
+007 public class Listing3807
+008 extends JFrame
+009 implements ActionListener
+010 {
+011 JTable table;
+012 SparseTableModel tableModel;
+013
+014 public Listing3807()
+015 {
+016 super("JTable 2");
+017 addWindowListener(new WindowClosingAdapter(true));
+018 tableModel = new SparseTableModel(1000);
+019 table = new JTable(tableModel, null);
+020 table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
+021 table.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
+022 table.setCellSelectionEnabled(true);
+023 Container cp = getContentPane();
+024 cp.add(new JScrollPane(table), BorderLayout.CENTER);
+025 JButton button = new JButton("Drucken");
+026 button.addActionListener(this);
+027 cp.add(button, BorderLayout.SOUTH);
+028 }
+029
+030 public void actionPerformed(ActionEvent event)
+031 {
+032 tableModel.printData();
+033 }
+034
+035 public static void main(String[] args)
+036 {
+037 Listing3807 frame = new Listing3807();
+038 frame.setLocation(100, 100);
+039 frame.setSize(320, 200);
+040 frame.setVisible(true);
+041 }
+042 }
+
+ |
++Listing3807.java | +
+Die Ausgabe des Programms sieht nach einigen Einfügungen so aus: +
+ +
+Abbildung 38.6: Eine JTable mit einer Million Zellen
+ +
+Wenn in diesem Zustand der »Drucken«-Button betätigt
+wird, gibt das Programm folgende Liste auf der Konsole aus:
+
+
+At [997,998]: große
+At [994,997]: Hallo
+At [999,999]: Welt
+At [996,999]: Tabellen-
+
+
+
+
+
+
+
+Neben dem Tabellenmodell, das die Daten der Tabelle enthält, +besitzt eine JTable +ein weiteres Modell, das für die Eigenschaften der Spalten verantwortlich +ist. In unseren bisherigen Beispielen wurde es implizit aus dem Tabellenmodell +und den angegebenen Spaltennamen erzeugt. Sollen neben den Namen weitere +Eigenschaften der Spalten kontrolliert werden, reicht das nicht aus, +und ein eigenes Spaltenmodell muss geschrieben werden. + +
+Das Spaltenmodell einer JTable +muss das Interface TableColumnModel +aus dem Paket javax.swing.table +implementieren und wird bei der Instanzierung einer JTable +an deren Konstruktor übergeben. Da die Implementierung eines +Spaltenmodells recht aufwändig ist, wurde mit der Klasse DefaultTableColumnModel +eine Standard-Implementierung geschaffen, die ohne weitere Ableitung +verwendet werden kann. Das zunächst leere Modell stellt Methoden +zur Verfügung, mit denen Spaltenobjekte (sie sind vom Typ TableColumn) +hinzugefügt oder entfernt werden können: +
+
+
++public void addColumn(TableColumn aColumn) +public void removeColumn(TableColumn column) ++ + |
++javax.swing.table.DefaultTableColumnModel | +
+Jede an das Modell übergebene Instanz der Klasse TableColumn +repräsentiert dabei die Eigenschaften einer einzelnen Tabellenspalte. +Mit einer TableColumn +können praktisch alle visuellen Eigenschaften der Spalte kontrolliert +werden. So kann die Breite ebenso wie die Spaltenposition festgelegt +werden, und es können beliebige Komponenten zur Darstellung und +zum Editieren der Zellen definiert werden. Wie wollen uns auf ein +einfaches Beispiel beschränken und lediglich zeigen, wie die +anfängliche Breite der Spalten explizit festgelegt werden kann. + +
+Dazu instanzieren wir ein DefaultTableColumnModel +und fügen drei TableColumn-Objekte +hinzu. Sie werden jeweils mit folgendem Konstruktor initialisiert: +
+
+
++public TableColumn(int modelIndex, int width) ++ + |
++javax.swing.table.TableColumn | +
+Der erste Parameter gibt den Modellindex an, also die Spalte im Tabellenmodell, +zu der die visuelle Spalte korrespondiert. Der zweite Parameter gibt +die initiale Breite der Spalte an. Anschließend rufen wir die +Methode setHeaderValue +auf, um die Spaltenbeschriftung zu definieren, und fügen die +Spalte in das Spaltenmodell ein. Das wiederholen wir für alle +drei Spalten und übergeben das Spaltenmodell an den Konstruktor +der Tabelle. Da bei Übergabe eines Spaltenmodells auch das Tabellenmodell +explizit übergeben werden muss, definieren wir es aus unserem +vorhandenen Datenarray durch eine lokale Ableitung der Klasse AbstractTableModel: + + +
+
+
+
+001 /* Listing3808.java */
+002
+003 import java.awt.*;
+004 import javax.swing.*;
+005 import javax.swing.table.*;
+006
+007 public class Listing3808
+008 extends JFrame
+009 implements TableData
+010 {
+011 public Listing3808()
+012 {
+013 super("JTable 3");
+014 addWindowListener(new WindowClosingAdapter(true));
+015 //Spaltenmodell erzeugen
+016 DefaultTableColumnModel cm = new DefaultTableColumnModel();
+017 for (int i = 0; i < COLHEADS.length; ++i) {
+018 TableColumn col = new TableColumn(i, i == 2 ? 150 : 60);
+019 col.setHeaderValue(COLHEADS[i]);
+020 cm.addColumn(col);
+021 }
+022 //Tabellenmodell erzeugen
+023 TableModel tm = new AbstractTableModel() {
+024 public int getRowCount()
+025 {
+026 return DATA.length;
+027 }
+028 public int getColumnCount()
+029 {
+030 return DATA[0].length;
+031 }
+032 public Object getValueAt(int row, int column)
+033 {
+034 return DATA[row][column];
+035 }
+036 };
+037 //Tabelle erzeugen und ContentPane füllen
+038 JTable table = new JTable(tm, cm);
+039 Container cp = getContentPane();
+040 cp.add(new JLabel("Alte c\'t-Ausgaben:"), BorderLayout.NORTH);
+041 cp.add(new JScrollPane(table), BorderLayout.CENTER);
+042 }
+043
+044 public static void main(String[] args)
+045 {
+046 Listing3808 frame = new Listing3808();
+047 frame.setLocation(100, 100);
+048 frame.setSize(350, 200);
+049 frame.setVisible(true);
+050 }
+051 }
+
+ |
++Listing3808.java | +
+Die initialen Spaltenbreiten wurden auf 60 bzw. 150 Zeichen festgelegt, +und die Ausgabe des Programms sieht so aus: +
+ +
+Abbildung 38.7: Eine JTable mit eigenem Spaltenmodell
+ + + + ++Als Rendering bezeichnet man den Vorgang, +der dafür sorgt, dass die Zellen auf dem Bildschirm dargestellt +werden. Die dafür verantwortlichen Komponenten werden als Renderer +bezeichnet. Eine JTable +besitzt einen Standard-Renderer, auf den mit den Methoden getDefaultRenderer +und setDefaultRenderer +zugegriffen werden kann: +
+
+
++public TableCellRenderer getDefaultRenderer(Class columnClass) +public void setDefaultRenderer(Class columnClass, TableCellRenderer renderer) ++ + |
++javax.swing.JTable | +
+Sofern nicht in den Tabellenspalten ein eigener Renderer bestimmt +wird, ist der Standard-Renderer für die Darstellung aller Tabellenelemente +zuständig. Er muss das Interface TableCellRenderer +implementieren. Es enthält nur eine einzige Methode: +
+
+
++public Component getTableCellRendererComponent( + JTable table, + Object value, + boolean isSelected, + boolean hasFocus, + int row, + int column +) ++ + |
++javax.swing.table.TableCellRenderer | +
+Diese arbeitet als Factory-Methode und wird immer dann aufgerufen, +wenn zur Darstellung einer Zelle ein Renderer benötigt wird. +Mit Hilfe der übergebenen Argumente kann der Renderer bestimmen, +für welche Zelle er aktiv werden soll, welchen Inhalt diese hat, +und ob sie gerade selektiert ist oder den Fokus hat. Zusätzlich +wird die Tabelle selbst übergeben, so dass der Renderer Zugriff +auf deren Eigenschaften und Modelle hat. + +
+Standardmäßig wird als Renderer eine Instanz der Klasse +DefaultTableCellRenderer +verwendet. Sie ist eine Ableitung von JLabel, +mit deren Hilfe Farbe, Font und Hintergrund an das Look-and-Feel der +Tabelle und die Erfordernisse der jeweiligen Zelle anpasst werden. +Interessanterweise wird pro Tabelle lediglich eine einzige Instanz +erzeugt und zur Darstellung aller Zellen verwendet. Dazu wird +das Label jeweils an die Position der darzustellenden Tabelle verschoben +und dann mit den erforderlichen visuellen Eigenschaften versehen. + +
+Da ein JLabel +für diese Art von Anwendung eigentlich nicht vorgesehen wurde, +muss DefaultTableCellRenderer +aus Performancegründen (insbesondere im JDK 1.3) einige der Standardmechanismen +von Swing-Komponenten deaktivieren oder umdefinieren. Aus diesem Grunde +ist das Ableiten einer eigenen Klasse aus DefaultTableCellRenderer +problematisch. Auch das Verwenden des Standard-Renderers in einer +eigenen Renderer-Implementierung (mit dem Ziel, nur die wirklich unterschiedlichen +Eigenschaften zu modifizieren) funktioniert nicht ohne weiteres. + +
+Das folgende Beispiel zeigt einen Renderer, dessen Aufgabe darin besteht, +die Zellen unserer schon bekannten Tabelle in unterschiedlichen Farben +darzustellen. Die Klasse DefaultTableCellRenderer +wird dazu weder per Ableitung noch per Delegation verwendet. + + +
+
+
+
+001 /* ColoredTableCellRenderer.java */
+002
+003 import java.awt.*;
+004 import javax.swing.*;
+005 import javax.swing.border.*;
+006 import javax.swing.table.*;
+007
+008 public class ColoredTableCellRenderer
+009 implements TableCellRenderer
+010 {
+011 private Color lightBlue = new Color(160, 160, 255);
+012 private Color darkBlue = new Color( 64, 64, 128);
+013
+014 public Component getTableCellRendererComponent(
+015 JTable table,
+016 Object value,
+017 boolean isSelected,
+018 boolean hasFocus,
+019 int row,
+020 int column
+021 )
+022 {
+023 //Label erzeugen
+024 JLabel label = new JLabel((String)value);
+025 label.setOpaque(true);
+026 Border b = BorderFactory.createEmptyBorder(1, 1, 1, 1);
+027 label.setBorder(b);
+028 label.setFont(table.getFont());
+029 label.setForeground(table.getForeground());
+030 label.setBackground(table.getBackground());
+031 if (hasFocus) {
+032 label.setBackground(darkBlue);
+033 label.setForeground(Color.white);
+034 } else if (isSelected) {
+035 label.setBackground(lightBlue);
+036 } else {
+037 //Angezeigte Spalte in Modellspalte umwandeln
+038 column = table.convertColumnIndexToModel(column);
+039 if (column == 1) {
+040 int numpages = Integer.parseInt((String)value);
+041 if (numpages >= 250) {
+042 label.setBackground(Color.red);
+043 } else if (numpages >= 200) {
+044 label.setBackground(Color.orange);
+045 } else {
+046 label.setBackground(Color.yellow);
+047 }
+048 }
+049 }
+050 return label;
+051 }
+052 }
+
+ |
++ColoredTableCellRenderer.java | +
+getTableCellRendererComponent +erzeugt bei jedem Aufruf ein neues JLabel, +dessen Beschriftung dem Zelleninhalt entspricht. Es bekommt einen +nicht-transparenten Hintergrund und einen unsichtbaren Rahmen von +einem Pixel Breite (damit die Zellen nicht direkt aneinanderstoßen). +Anschließend werden Schriftart, Vorder- und Hintergrundfarbe +von der Tabelle übernommen. + +
+Ab Zeile 031 beginnt die +Definition der Vorder- und Hintergrundfarbe. Hat das Element den Fokus, +wird es in dunkelblau auf weiß gezeichnet. Ist es lediglich +selektiert, wird der Hintergrund hellblau eingefärbt. Ist beides +nicht der Fall, prüft die Methode, ob das darzustellende Element +aus der Spalte mit den Seitenzahlen stammt. Dazu ist es zunächst +nötig, in Zeile 038 +den visuellen Spaltenwert in die korrespondierende Modellspalte umzurechnen +(vertauscht der Anwender Spalten, unterscheiden sich beide Werte). +Abhängig von der vorgefundenen Seitenzahl wird der Hintergrund +dann gelb, orange oder rot dargestellt. + +
+Dieser Renderer kann sehr leicht durch Aufruf von setDefaultRenderer +in die Tabelle integriert werden: + + +
+
+
+
+001 /* Listing3810.java */
+002
+003 import java.awt.*;
+004 import javax.swing.*;
+005
+006 public class Listing3810
+007 extends JFrame
+008 implements TableData
+009 {
+010 public Listing3810()
+011 {
+012 super("JTable 4");
+013 addWindowListener(new WindowClosingAdapter(true));
+014 JTable table = new JTable(DATA, COLHEADS);
+015 table.setDefaultRenderer(
+016 Object.class,
+017 new ColoredTableCellRenderer()
+018 );
+019 Container cp = getContentPane();
+020 cp.add(new JLabel("Alte c\'t-Ausgaben:"), BorderLayout.NORTH);
+021 cp.add(new JScrollPane(table), BorderLayout.CENTER);
+022 }
+023
+024 public static void main(String[] args)
+025 {
+026 Listing3810 frame = new Listing3810();
+027 frame.setLocation(100, 100);
+028 frame.setSize(350, 200);
+029 frame.setVisible(true);
+030 }
+031 }
+
+ |
++Listing3810.java | +
+Die Ausgabe des Programms sieht nun so aus: +
+ +
+Abbildung 38.8: Eine Tabelle mit einem eigenen Zellrenderer
++
![]() |
+![]() |
+
+
+ +Der Renderer erzeugt bei jedem Aufruf von getTableCellRendererComponent +eine neue Instanz der Klasse JLabel. +Da das während der Arbeit mit der Tabelle sehr häufig erfolgt +(schon das Bewegen des Mauszeigers über der Tabelle löst +etliche Aufrufe aus), ist diese Vorgehensweise recht ineffizient und +belastet den Garbage Collector. In »echten« Programmen sollte +daher mehr Aufwand getrieben werden. So könnten beispielsweise +Renderer in einem Cache zwischengespeichert und bei erneutem Bedarf +wiederverwendet werden. Oder das Programm könnte eine Technik +ähnlich der von DefaultTableCellRenderer +verwenden und nur eine einzige Instanz erzeugen. Die Lektüre +des Quelltextes der Klasse zeigt, wie es gemacht wird. |
+
+
|
+![]() |
+
+Eine JTable +generiert eine Vielzahl von Ereignissen, um registrierte Listener +über Änderungen des Tabellenzustands zu informieren. Will +ein Objekt beispielsweise darüber informiert werden, dass sich +die Selektion geändert hat, muss es zwei ListSelectionListener +registrieren. Einer davon wird auf dem Selektionsmodell registriert, +das mit getSelectionModel +ermittelt werden kann. Da dieser nur Informationen über Änderungen +an der Zeilenselektion versendet, muss ein zweiter Listener auf dem +Modell für die Spaltenselektion registriert werden. Es kann durch +Aufruf von getColumnModel +beschafft werden, und auf sein Selektionsmodell kann ebenfalls mit +getSelectionModel +zugegriffen werden. Bei jeder Änderung der Selektion wird nun +valueChanged +aufgerufen und kann mit Hilfe der oben erläuterten Methoden herausfinden, +welche Zeilen und Spalten selektiert sind. + +
+Die Tabelle informiert auch über Änderungen ihrer Daten. +Dazu muss auf dem Tabellenmodell (das mit getModel +beschafft wird) durch Aufruf von addTableModelListener +ein TableModelListener +registriert werden. Bei jeder Änderung des Modells wird dann +dessen Methode tableChanged +aufgerufen. + +
+Schließlich können auch alle in den Vaterklassen von JTable +definierten Listener registriert werden. Soll beispielsweise auf einen +Klick mit der rechten Maustaste reagiert werden, kann durch Aufruf +von addMouseListener +ein MouseListener +registriert werden. Innerhalb seiner Ereignismethoden kann mit getX +und getY +die aktuelle Mausposition abgefragt und mit den Methoden rowAtPoint +und columnAtPoint +in Zeilen- und Spaltenwerte der Tabelle umgerechnet werden: +
+
+
++public int rowAtPoint(Point point) +public int columnAtPoint(Point point) ++ + |
++javax.swing.JTable | +
| Titel + | Inhalt + | Suchen + | Index + | DOC + | Handbuch der Java-Programmierung, 5. Auflage, Addison +Wesley, Version 5.0.1 + |
| << + | < + | > + | >> + | API + | © 1998, 2007 Guido Krüger & Thomas +Stark, http://www.javabuch.de + |