![]() |
|
|||||
15.26.2 AbstractTableModel
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Beispiel Wenn wir eine Tabelle mit Quadrat und Kubik nutzen, dann können wir ein Modell implementieren, das in der ersten Spalte die Zahl, in der zweiten das Quadrat und in der dritten das Kubik abbildet. Die Tabelle besitzt damit drei Spalten. Sie soll zehn Zeilen groß sein. |

Hier klicken, um das Bild zu Vergrößern
Listing 15.36 QuadratTable.java, QuadratTableModelSimple
package 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.24 JTable mit Model
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 zusätzliche Dinge in einer fire()-Methode realisieren.
| Beispiel Ändern sich die Daten, muss die Visualisierung erneuert werden. 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)); }
Kommt ein JTable ohne eigenes Modell zum Einsatz, nutzt die Implementierung eine Unterklasse von AbstractTableModel, welche sich DefaultTableModel nennt. Sie ist nicht abstrakt und könnte auch von uns verwendet werden. Nützliche Ergänzungen sind Methoden, damit an beliebiger Stelle Zellen eingetragen, verschoben und gelöscht werden können. Nutzen wir JTable ohne eigenes Modell, 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 gewünscht, da dynamische Strukturen von der Laufzeit her ineffizient sein können. Ist das zu unflexibel, lässt sich immer noch ein eigenes Modell von AbstractTableModel ableiten.
Besitzen wir eine Klasse, die ein TableModel implementiert, etwa eine Unterklasse von AbstractTableModel oder DefaultTableModel, so können wir ein JTable mit diesem Modell anlegen. Dafür gibt es zwei Möglichkeiten: im Konstruktor das Modell angeben oder es nachträglich mit setModel() zuweisen.
Beispiel Das QuadratTableModelSimple soll unserer Tabelle zugewiesen werden.
TableModel model = new QuadratTableModelSimple(); JTable table = new JTable(); table.setModel( model ); |

Hier klicken, um das Bild zu Vergrößern
Damit eine Tabelle nicht nur die typischen Informationen in Zeichenketten darstellen muss, lässt sich ein TableCellRenderer einsetzen, mit dem sich die Tabelleneinträge beliebig visualisieren lassen. 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, ist die Zelle mit einer Hintergrundfarbe ausgeschmückt. |
Glücklicherweise bietet Swing wieder eine Standardimplementierung in Form der Klasse DefaultTableCellRenderer. Diese erweitetert JLabel. 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.

Hier klicken, um das Bild zu Vergrößern
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 es 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.
Beispiel In einer Tabelle befinden sich nur Zahlen. Ist eine Zahl negativ, so soll sie rot erscheinen.
public void setValue( Object value ) { String s = value.toString(); if ( Integer.parseInt( s ) < 0 ) setForeground( Color.red ); 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 Modell 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 mit dem Namen 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 super.setValue( value ); } } |
Die Behandlung im else-Zweig ist dabei sehr wichtig, denn dort wird der Rest der Daten behandelt. Ist es Text, so kümmert sich die Implementierung von DefaultTableCellRenderer darum. Bei setIcon() profitieren wir wieder von der Erweitung 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.37 ColoredTableCellRenderer.java package table; import java.awt.*; import javax.swing.table.*; class ColoredTableCellRenderer extends DefaultTableCellRenderer { public void setValue( Object value ) { if ( value instanceof Long ) { if ( ((Long)value).longValue()%2 == 0 ) setForeground( Color.blue ); else setForeground( Color.gray ); setText( ""+value ); } else super.setValue( value ); } } |
Um diesen zuzuweisen, verbinden wir einen Datentyp einschließlich Renderer mit der Methode setDefaultRenderer() von JTable.
DefaultTableCellRenderer ren = new ColoredTableCellRenderer(); table.setDefaultRenderer( Long.class, ren );
Der DefaultTableCellRenderer ist eine Unterklasse von JLabel. Diese unterstützt mehrzeilige Textfelder nicht direkt. Eine Lösung wäre jedoch, die HTML-Darstellung einzuschalten, im Text also etwa <HTML>Zeile1<BR>Zeile2</HTML> zu schreiben. Eine andere Möglichkeit ist es, einen eigenen Renderer zu implementieren, der nicht von DefaultTableCellRenderer abgeleitet ist. Eine gute 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 table/DoubleTeamTextCellRenderer.java
package table; import java.awt.*; import javax.swing.*; import javax.swing.table.*; public class DoubleTeamTextCellRenderer extends JTextArea implements TableCellRenderer { public Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column ) { // Text setzen setText( "1\n2" ); return this; } }
Alle Zelleninformationen der Tabelle stecken im Modell 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, so kann auch die Funktion getColumn(index) genutzt werden. Liegt ein TableColumn vor, so 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 ebenfalls 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.
| << zurück |
Copyright (c) Galileo Press GmbH 2004
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.