![]() |
|
|
Wollen wir auf die inneren Daten zugreifen, benötigen wir das TableModel. Über getModel() lässt sich dies von der JTable erfragen. Wir können die Tabelle auch fragen, welche Zelle selektiert ist. int col = t.getSelectedColumn(); int row = t.getSelectedRow(); System.out.println( t.getModel().getValueAt(row, col) ); 15.25.2 AbstractTableModel
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Beispiel Wenn wir eine Tabelle mit Quadrat und Kubik nutzen, können wir ein Model implementieren, das in der ersten Spalte die Zahl, in der zweiten das Quadrat und in der dritten das Kubik abbildet. Die Tabelle verfügt dann über drei Spalten. Sie soll zehn Zeilen groß sein. |

Hier klicken, um das Bild zu Vergrößern
Listing 15.34 com/javatutor/insel/ui/table/QuadratTableModelSimple
package com.javatutor.insel.ui.table; class QuadratTableModelSimple extends AbstractTableModel { public int getRowCount() { return 10; } public int getColumnCount() { return 3; } public Object getValueAt( int row, int col ) { if ( col == 0 ) return "" + row; else if ( col == 1 ) return "" + (row * row); else return "" + (row * row * row); } }

Hier klicken, um das Bild zu Vergrößern
Abbildung 15.23 JTable mit Model
Verfügen wir über eine Klasse, die ein TableModel implementiert, etwa eine Unterklasse von AbstractTableModel oder DefaultTableModel, so können wir ein JTable-Objekt mit diesem Modell verbinden. Dafür gibt es zwei Möglichkeiten: im Konstruktor das Model angeben oder es nachträglich mit setModel() zuweisen.
| Beispiel Das QuadratTableModelSimple soll unserer Tabelle zugewiesen werden.
Listing 15.35 com/javatutor/insel/ui/table/QuadratTable.java, Ausschnitt TableModel model = new QuadratTableModelSimple(); JTable table = new JTable(); table.setModel( model ); // oder new JTable( model ) |

Hier klicken, um das Bild zu Vergrößern
Standardmäßig lassen sich die Zellinhalte nicht ändern. Wenn der Anwender auf eine Zelle klickt, wird es kein Textfeld geben, das eine neue Eingabe ermöglicht. Das ändert sich aber, wenn aus der Schnittstelle TableModel die Methode boolean isCellEditable(int rowIndex, int columnIndex) überschrieben wird und immer dann true liefert, wenn ein Editor eine Änderung der Zelle erlauben soll. Ist diese Änderung für alle Zellen gültig, liefert die Methode immer true; soll zum Beispiel nur die erste Spalte verändert werden dürfen, schreiben wir zum Beispiel:
public boolean isCellEditable( int rowIndex, int columnIndex ) { return columnIndex == 0; }
Die Methode isCellEditable() ist aber nur der erste Teil einer Zelländerung. Die JTable (vereinfachen wir es mal) fragt zunächst beim Model über isCellEditable(), ob eine Zelle vom Anwender überhaupt modifiziert werden kann. Wenn das Ergebnis false ist, wird kein Editor angezeigt. Falls das Ergebnis true ist, sucht die JTable einen passenden Editor und ruft nach einer Änderung mit dem neuen Wert die Methode setValueAt(Object aValue, int rowIndex, int columnIndex) auf. Hier muss das Ergebnis in den Datenstrukturen auch wirklich gespeichert werden. Anschließend erfragt die JTable über getValueAt() noch einmal den aktuellen Wert.
Beispiel Über setValueAt() bekommen wir den neuen Wert als erstes Argument. Interessiert uns der alte Wert, können wir ihn aus dem Model erfragen.
void setValueAt( Object aValue, int rowIndex, int columnIndex ) { Object oldValue = getValueAt( rowIndex, columnIndex ); } |
Die Events, die AbstractTableModel auslöst, sind vom Typ TableModelEvent und werden von fireTableDataChanged(), fireTableStructureChanged(), fireTableRowsInserted(), fireTableRowsUpdated(), fireTableRowsDeleted(), fireTableCellUpdated() über die allgemeine Methode fireTableChanged(TableModelEvent) behandelt. Die Methoden zur Ereignisbehandlung sind damit vollständig und müssen von Unterklassen nicht mehr überschrieben werden, es sei denn, wir wollten in einer fire()-Methode Zusätzliches realisieren.
| Beispiel Ändern sich die Daten, ist die Visualisierung zu erneuern. Dann sollte fireTableCellUpdated() aufgerufen werden, wie für die setValueAt()-Methode gezeigt wird. |
public void setValueAt( Object val, int row, int column ) { foo[row][column] = aValue; fireTableCellUpdated( row, column ); }
Die Methode fireTableCellUpdated(int, int) ist nur eine Abkürzung für Folgendes:
public void fireTableCellUpdated(int row, int column) { fireTableChanged(new TableModelEvent(this, row, row, column)); }
Praktischerweise bringt die Java-Bibliothek schon eine Model-Klasse mit, die wir direkt verwenden. Es ist DefaultTableModel und ebenso eine Unterklasse von Abstract-TableModel. Nützliche Ergänzungen sind Methoden, damit an beliebiger Stelle Zellen eingetragen, verschoben und gelöscht werden können. Nutzen wir JTable ohne eigenes Model, so verwendet es standardmäßig DefaultTableModel mit einer Implementierung von Vektoren aus Vektoren. Ein Hauptvektor speichert Vektoren für jede Zeile. Die Technik lässt sich gut an einer Methode ablesen, die ein Wert erfragt:
public Object getValueAt( int row, int column ) { Vector rowVector = (Vector) dataVector.elementAt( row ); return rowVector.elementAt( column ); }
Mit den Methoden setDataVector() und getDataVector() lassen sich die Daten intern setzen und auslesen. Diese interne Abbildung der Daten ist jedoch nicht immer erwünscht, da dynamische Strukturen von der Laufzeit her ineffizient sein können. Ist das zu unflexibel, lässt sich immer noch ein eigenes Model von AbstractTableModel ableiten.
Damit eine Tabelle nicht nur die typischen Informationen in Zeichenketten darstellen muss, lässt sich ein TableCellRenderer einsetzen, mit dem man die Tabelleneinträge beliebig visualisieren kann. Die Schnittstelle TableCellRenderer schreibt nur eine Methode vor.
interface javax.swing.table. TableCellRenderer |
| Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) |
Die Informationen über isSelected, hasFocus, row und column sollen der Zeichenfunktion die Möglichkeit geben, ausgewählte Tabellenteile besonders zu behandeln. Steht etwa auf einer Zelle der Fokus, ist ein Rahmen gezeichnet. Ist die Tabelle selektiert, so ist die Zelle mit einer Hintergrundfarbe ausgeschmückt.
Glücklicherweise bietet Swing wieder eine Standardimplementierung in Form der Klasse DefaultTableCellRenderer. Diese Klasse erweitetert JLabel, und damit lässt sich schon viel anfangen.
Das Ändern des Textes ist genauso einfach wie das Ändern der Farbe oder das Hinzufügen eines Bilds. Viele Aufgaben sind so schon erledigt. Wenn es aufwändiger realisiert werden soll, dann müssen wir direkt TableCellRenderer implementieren.
Für unsere Zwecke soll DefaultTableCellRenderer genügen. Die wichtigste Methode zum Überschreiben ist setValue(Object). In DefaultTableCellRenderer sieht die Originalmethode wie folgt aus:
protected void setValue( Object value ) { setText( (value == null) ? "" : value.toString() ); }
Da JTable diesen Renderer als Standard nutzt, sagt dies aus, dass alle Daten in der Tabelle als String-Repräsentation eingesetzt werden.
Wenn wir eigene Visualisierungen wünschen, zum Beispiel mit einer anderen Schriftfarbe, so überschreiben wir einfach setValue() und setzen den Text mit setText() selbst.

Hier klicken, um das Bild zu Vergrößern
Beispiel In einem Tabellen-Model befinden sich Zahlen (bzw. deren String-Repräsentation sind Zahlen). Ist eine Zahl negativ, so soll sie rot erscheinen, andernfalls schwarz.
public void setValue( Object value ) { String s = value.toString(); if ( Integer.parseInt( s ) < 0 ) setForeground ( Color.RED ); else setForeground( Color.BLACK ); setText ( s ); } |
Die günstige Eigenschaft, dass DefaultTableCellRenderer eine Unterklasse von JLabel ist, macht sich bei setForeground() bemerkbar. Für mehrzeiligen Text machen sich die Unterklassen von JText ganz gut.
Liegen im Model einer JTable nicht nur Daten einer Gattung, so lassen sie sich mit instanceof aufschlüsseln.
Beispiel In einer Tabelle sollen Zahlen (etwa vom Typ Integer) und Objekte vom Typ Gfx liegen. Gfx-Objekte enthalten ein Icon-Objekt namens icon. Es soll in die Tabelle gesetzt werden.
public void setValue( Object value ) { if ( value instanceof Gfx ) { Gfx gfx = (IconData) value; |
setIcon( gfx.icon );
}
else {
setIcon( null );
super. setValue ( value );
}
}
|
Die Behandlung im else-Zweig ist sehr wichtig, weil dort der Rest der Daten behandelt wird. Handelt es sich um Text, kümmert sich die Implementierung von DefaultTable-CellRenderer darum. Bei setIcon() profitieren wir wieder von der Erweiterung von JLabel.
| Beispiel Unserer Tabelle mit den Quadrat- und Kubikzahlen wollen wir einen Renderer mitgeben. Er soll die geraden Zahlen in Blau anzeigen und die ungeraden in Grau.
Listing 15.36 com/javatutor/insel/ui/table/ColoredTableCellRenderer.java package com.javatutor.insel.ui.table; import java.awt.*; import javax.swing.table.*; class ColoredTableCellRenderer extends DefaultTableCellRenderer { @Override public void setValue( Object value ) { if ( value instanceof Long ) { setForeground( (Long)value % 2 == 0 ? Color.BLUE : Color.GRAY ); setText( value.toString() ); } else super.setValue( value ); } }Die Typanpassung (Long)value veranlasst den Compiler mit Unboxing aus dem value-Objekt, das long zu extrahieren. |
Ein Renderer übernimmt nicht die Darstellung von allen Zellen, sondern nur die von bestimmten Typen. Daher erwartet die Methode setDefaultRenderer() von JTable neben dem Renderer ein Class-Objekt. Nimmt die JTable aus dem Model ein Objekt heraus, erfragt es den Typ und lässt den Zelleninhalt vom Renderer, der mit diesem Typ verbunden ist, zeichnen.
Listing 15.37 com/javatutor/insel/ui/table/QuadratTableWithRenderer.java, Ausschnitt
DefaultTableCellRenderer ren = new ColoredTableCellRenderer(); table. setDefaultRenderer ( Long.class, ren );
Stellt die Tabelle ein Element vom Typ Long.class da, so überlässt sie die Visualisierung dem zugewiesenen ColoredTableCellRenderer. Object.class passt auf alle Zellen.
Der DefaultTableCellRenderer ist eine Unterklasse von JLabel, der mehrzeilige Textfelder durch die HTML-Darstellung unterstützt. Für einen Text müsste etwa <HTML>Zeile1-<BR>Zeile2</HTML> geschrieben werden. Eine andere Möglichkeit besteht darin, einen eigenen Renderer zu implementieren, der nicht von DefaultTableCellRenderer abgeleitet ist. Eine weitere Lösung ist, JTextArea als Oberklasse zu nutzen und die notwendige Schnittstelle TableCellRenderer zu implementieren. Die implementierte Methode getTableCellRendererComponent() liefert dann das this-Objekt zurück, gesetzt mit dem Text inklusive Zeilenumbruch.
Listing 15.38 com/javatutor/insel/ui/table/TowLinesCellRenderer.java
package com.javatutor.insel.ui.table; import java.awt.*; import javax.swing.*; import javax.swing.table.*; public class TwoLinesCellRenderer extends JTextArea implements TableCellRenderer { public Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column ) { setText( "1\n2" ); // Text setzen, hier z.B. 1 2 return this; } }
Anders als bei der JList kann der Benutzer die Zellen einer JTable editieren. Erlaubt das Tabellenmodel eine Veränderung, so stellt die JTable vordefinert eine Texteingabezeile dar. Ein eigener Editor implementiert die Schnittstelle javax.swing.table.TableCellEditor mit einer Funktion getTableCellEditorComponent(), die die Editor-Komponente liefert. Das kann zum Beispiel ein JTextField sein. Nach der Bearbeitung erfragt die JTable das Ergebnis über die Methode getCellEditorValue(). Auch diese Funktion schreibt die Schnittstelle (indirekt) vor.
Listing 15.39 com/javatutor/insel/ui/table/SimpleTableCellEditor.java
package com.javatutor.insel.ui.table; import java.awt.Component; import javax.swing.*; import javax.swing.table.TableCellEditor; public class SimpleTableCellEditor extends AbstractCellEditor implements TableCellEditor { private JTextField component = new JTextField(); public Component getTableCellEditorComponent( JTable table, Object value, boolean isSelected, int rowIndex, int colIndex ) { component.setText( value.toString() ); return component; } public Object getCellEditorValue() { return component.getText(); } }
Die Schnittstelle TableCellEditor selbst definiert nur die Funktion getTableCell-EditorComponent(), doch weil CellEditor die Ober-Schnittstelle ist, ergeben sich insgesamt 1 + 7 zu implementierende Funktionen. CellEditor ist eine ganz allgemeine Schnittstelle für beliebige Zellen, etwa auch die Zellen in einem JTree-Objekt. Die abstrakte Basisklasse AbstractCellEditor implementiert bis auf getCellEditorValue() alle Funktionen aus CellEditor. Und da unsere Klasse die Schnittstelle TableCellEditor annehmen muss, bleibt es bei der Implementierung von getCellEditorValue() und getTableCellEditor-Component().
Jede Zelle hat eine bestimmte Größe, die durch ihren Inhalt vorgegeben ist. Zusätzlich liegt zwischen zwei Zellen immer etwas Freiraum (engl. gap). Dieser lässt sich erfragen mit getIntercellSpacing().
Dimension d = table.getIntercellSpacing(); // d.width == 1, d.height == 1
Der JTable kann global mit setIntercellSpacing() dieser Zwischenraum (engl. margin) oben/unten und rechts/links zugewiesen werden.
table.setIntercellSpacing( new Dimension(gapWidth, gapHeight) );
Soll die Zelle rechts und links 2 Pixel mehr bekommen, ist gapWidth auf 4 zu setzen.
Die Gesamtgröße einer Zelle ist dann die der Margin Zeile + Zellhöhe bzw. Margin Spalte + Zellbreite. Da jedoch setIntercellSpacing() die Höhe einer Zeile nicht automatisch anpasst, muss sie ausdrücklich gesetzt werden:
table.setRowHeight( table.getRowHeight() + gapHeight );
Zusätzlich zur Margin erhöht eine Linie den Abstand zwischen den Zellen. Auch dieses Raster (engl. grip) lässt sich modifizieren. Die folgenden Funktionen sind angewendet auf die JTable.
| setShowGrid( false ); | Schaltet die Umrandung aus |
| setShowGrid( false ); setShowVerticalLines( true ); | Zeigt nur vertikale Linien |
| setGridColor( Color.GRAY ); | Die Umrandung wird grau |
Alle Zelleninformationen der Tabelle stecken im Model einer JTable. Doch Informationen über die Spalten werden nicht im TableModel abgespeichert, sondern in Objekten vom Typ TableColumn. Jede Spalte bekommt ein eigenes TableColumn-Objekt, und eine Sammlung der Objekte bildet das TableColumnModel, welches wie das TableModel ein Datencontainer der JTable ist.
Beispiel Zähle alle TableColumn-Objekte einer JTable table auf.
for ( Enumeration enum = table.getColumnModel().getColumns(); enum.hasMoreElements(); ) System.out.println( (TableColumn)enum.nextElement() ); |
getColumns() bezieht eine Enumeration von TableColumn-Objekten. Soll ein ganz bestimmtes TableColumn untersucht werden, kann auch die Funktion getColumn(index) genutzt werden. Liegt ein TableColumn vor, lässt sich von diesem die aktuelle minimale und maximale Breite setzen.
Beispiel Ändere von der ersten Spalte die Breite auf 100 Pixel.
TableColumn col = table.getColumnModel().getColumn( 0 ); col.setPreferredWidth( 100 ); |
Der Kopf (engl. header) einer JTable ist ein JTableHeader-Objekt, welches von der JTable mit getTableHeader() erfragt werden kann. Dieses JTableHeader-Objekt ist für die Anordnung und Verschiebung der Spalten verantwortlich. Diese Verschiebung kann über das Programm erfolgen (moveColumn()) oder über den Benutzer per Drag & Drop.
| Beispiel In der JTable table sollen die Spalten nicht mehr vom Benutzer verschoben werden können. Er soll auch die Breite nicht mehr ändern dürfen. |
table.getTableHeader().setReorderingAllowed( false ); table.getTableHeader().setResizingAllowed( false ); |
Hier wird deutlich, dass ein JTableHeader die Steuerung der Ausgabe und der Benutzerinteraktion übernimmt, aber in TableColumn die Informationen selbst liegen.
In einer JTable können auf unterschiedliche Art und Weise Zellen selektiert werden: zum einen nur in einer Zeile oder Spalte, dann einmal ein ganzer Block oder auch beliebige Zellen. Die Art der Selektion bestimmen Konstanten in ListSelectionModel. So wird SINGLE_SELECTION nur die Selektion einer einzigen Zelle zulassen.
Beispiel In einer JTable sollen entweder ein ununterbrochener Block Zeilen oder Spalten ausgewählt werden dürfen:
table.setSelectionMode( ListSelectionModel.SINGLE_INTERVAL_SELECTION ); |
In Abhängigkeit eines gültigen Selektionsmodus lassen sich über Methoden alle Elemente einer Spalte oder Zeile selektieren. Das Selektieren erlauben jedoch erst zwei Funktionen.
table.setColumnSelectionAllowed( boolean ); table.setRowSelectionAllowed( boolean );
Die Selektion von Spalten gelingt mit setColumnSelectionInterval(), weitere Bereiche lassen sich mit addColumnSelectionInterval() hinzufügen und mit removeColumnSelectionInterval() entfernen. Das Gleiche gilt für die Methoden, die Row im Methodennamen tragen.
Schauen wir uns einige Beispiele an: Selektiere in einer JTable table Spalte 0 komplett.
table.setSelectionMode( ListSelectionModel.MULTIPLE_INTERVAL_SELECTION ); table.setColumnSelectionAllowed( true ); table.setRowSelectionAllowed( false ); table.setColumnSelectionInterval( 0, 0 );
Selektiere in einer Tabelle nur die Zelle 38, 5.
table.setSelectionMode( ListSelectionModel.SINGLE_SELECTION ); table.setColumnSelectionAllowed( true ); table.setRowSelectionAllowed( true ); table.changeSelection( 38, 5, false, false );
Als Selektionsmodus reicht SINGLE_SELECTION aus, MULTIPLE_INTERVAL_SELECTION wäre aber auch in Ordnung. Beide Selektionen sind zusammen in der Form nicht möglich. Bei einer Einzelselektion wird die Zelle nur umrandet, aber nicht wie beim Standard-Metal-Look&Feel blau ausgefüllt.
Die Methode selectAll() selektiert alle Elemente, clearSelection() löscht alle Selektionen.
Eine JTable ist zwar eine einfache Komponente, doch schon in der HTML-Tabelle gibt es Eigenschaften, die die JTable nicht abbilden kann: Zellen, die über mehrere Zeilen und Spalten gehen. Hier ist einiges an Programmieraufwand nötig, doch unnötig für diejenigen, die auf die freie Komponente JGrid zurückgreifen. Die Swing-Komponente gehört zu Pepper (http://jeppers.sourceforge.net/), einer Komponente für Tabellenkalkulationen, die sogar eine Formelauswertung ähnlich Excel versteht. Pepper selbst liegt zwar unter der GPL, doch JGrid ist LGPL und lässt sich somit in eigene kommerzielle Anwendungen einbinden.
| << zurück |
Copyright © Galileo Press GmbH 2005
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.