JTable (Tutorial)©
Inhaltsverzeichnis
Teil 1 - Einführung
Von allen Components in ganz AWT und Swing, ist das JTable wohl die komplexeste überhaupt (allenfalls kann der JTree noch mithalten). Jedenfalls genug kompliziert, dass fast jede 10. Frage im Forum irgendwas mit JTable zu tun hat.
Ziel ist es, dem Leser einen Überblick zu verschaffen, und ihn an die wichtigsten Konzepte heranzuführen. Dabei soll hier nur jedes Themengebiet für sich alleine betrachtet werden, die Kombination aller TableModels, CellRenderer, TableHeaders, ColumnModels und CellEditors können hier nicht behandelt werden, da wir schliesslich kein Buch schreiben wollen.
Dieses Tutorial ist eigentlich eine grosse Sammlung von Beispielen. Am meisten lernt man immer noch, wenn man selbst rumspielen darf, deshalb: kopiere alle Beispiele und führ sie aus. Verändere ein paar Werte, und schau was passiert. Versuch selbst was sinnvolles zu schreiben.
Dabei konnte nicht immer die beste Implementation gewählt werden, wiel dies das Konzept (von vorne beginnen) gründlich durcheinander gebracht hätte.
1. Die JTable
Sollte diese Component wirklich unbekannt sein, von dem hier sprechen wir:
2. API
Anlaufstelle Nr. 1 ist, wie so oft, die API Das JTable befindet sich im Package javax.swing Alle weiteren Interfaces und Klassen, welche für das JTable benötigt werden, befinden sich im Package javax.swing.table
3. Tutorials und anderes im WWW
Das How to use JTable von Sun bietet einen einfachen Überblick. Javatip 116 bietet ebenfalls eine kleine Einführung. Javatip 102 beschäftigt sich mit dem CellEditor, insbesondere mit verschiedenen CellEditors in derselben Column. Ein weiteres Tutorial und eine thematische Sortierung von Methoden, etc... findet sich in dem Handout des Kurses Human Computer Interaction Bei Codeguru finden sich interessante Codeausschnitte zu allen möglichen Dingen - z.B. einem Hintergrundbild für das JTable. Die jDynTable ist eine Erweiterung der normalen JTable, und erlaubt auch verschmolzene Zellen (Zellen über mehrere Spalten).
4. Wichtige Bemerkung In allen Beispielen wird ein JScrollPane benutzt. Erst das JScrollPane macht es dem JTable möglich, eine Titelleiste anzuzeigen, und gibt ihm die Möglichkeit, umhergeschoben zu werden. <code=java> // Das JTable JTable table = ...
// Der Container, dem das JTable hinzugefügt wird Container content = ...
// Ein JScrollPane erstellen, und zwischen dem Table und dem Container einfügen. content.add( new JScrollPane( table )); </code=java>
Teil 2 - Wie kommen Daten in die Tabelle?
Das JTable ist eine sehr anpassungsfähige GUI-Componente. Doch wie kommen eigentlich die Daten in das JTable hinein?
1. Möglichkeit - 2d Array
Die wohl einfachste Methode ist einen 2-dimensionalen Array zu benutzen. Das JTable bietet einen entsprechenden Konstruktor: <code=java>import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTable;
public class JTableDemo{ public static void main( String[] args ){ // Die Daten für das Table String[][] data = new String[][]{ {"a", "b", "c", "d"}, {"e", "f", "g", "h"}, {"i", "j", "k", "l"} };
// Die Column-Titles String[] title = new String[]{ "A", "B", "C", "D" };
// Das JTable initialisieren JTable table = new JTable( data, title );
JFrame frame = new JFrame( "Demo" ); frame.getContentPane().add( new JScrollPane( table ) ); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.pack(); frame.setVisible( true ); } } </code=java>
Diese Methode ist sehr statisch, es ist später nicht mehr gut möglich, Daten zu ändern.
2. Möglichkeit - Vectoren
Mit ineinander gesetzten Vektoren: <code=java> import java.util.Vector;
import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.table.AbstractTableModel;
public class JTableDemo{ public static void main( String[] args ){ // Die Daten für das Table Vector data = new Vector(); Vector rowA = new Vector(); rowA.add( "1" ); rowA.add( "2" ); rowA.add( "3" ); rowA.add( "4" ); Vector rowB = new Vector(); rowB.add( "5" ); rowB.add( "6" ); rowB.add( "7" ); rowB.add( "8" ); Vector rowC = new Vector(); rowC.add( "9" ); rowC.add( "10" ); rowC.add( "11" ); rowC.add( "12" );
data.add( rowA ); data.add( rowB ); data.add( rowC );
// Die Titel für das Table Vector title = new Vector(); title.add( "A" ); title.add( "B" ); title.add( "C" ); title.add( "D" );
// Das JTable initialisieren JTable table = new JTable( data, title );
JFrame frame = new JFrame( "Demo" ); frame.getContentPane().add( new JScrollPane( table ) ); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.pack(); frame.setVisible( true ); } } </code=java>
Das hat natürlich den Vorteil, dass man die Vektoren an verschiedenen Orten zusammenbauen kann, doch auch hier gilt: nachdem das JTable initialisiert wurde, kann man nichts mehr verändern.
3. Möglichkeit - DefaultTableModel
Mit einem DefaultTableModel.
Ein DefaultTableModel kann man sich auch als 2-dimensionalen Array vorstellen. Allerdings besitzt es Methoden, um neue Rows/Columns hinzuzufuegen, oder zu entfernen, z.B. addRow.
Was ein TableModel ist, wird im naechsten Teil behandelt. <code=java> import java.awt.BorderLayout; import java.awt.Container; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Vector;
import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.table.DefaultTableModel;
public class JTableDemo{ public static void main( String[] args ){ // Die Namen der Columns String[] titles = new String[]{ "A", "B", "C", "D" };
// Das Model das wir verwenden werden. Hier setzten wir gleich die // Titel, aber es ist später immer noch möglich weitere Columns oder // Rows hinzuzufügen. final DefaultTableModel model = new DefaultTableModel( titles, 0 );
// Das JTable initialisieren JTable table = new JTable( model );
// Buttons, damit das alles schöner aussieht. final JButton buttonAddRow = new JButton( "add row" ); final JButton buttonRemRow = new JButton( "remove row" ); final JButton buttonAddCol = new JButton( "add column" );
buttonRemRow.setEnabled( false );
// Den Buttons ein paar Reaktionen geben buttonAddRow.addActionListener( new ActionListener(){ public void actionPerformed(ActionEvent e) { // Die Anzahl Columns (Breite) der Tabelle int size = model.getColumnCount();
// einen neuen Vector mit Daten herstellen Vector newDatas = createDataVector( "row", size );
// eine neue Row hinzufügen model.addRow( newDatas );
// das Entfernen erlauben
buttonRemRow.setEnabled( true );
}
});
buttonAddCol.addActionListener( new ActionListener(){ public void actionPerformed(ActionEvent e) { int size = model.getRowCount(); Vector newDatas = createDataVector( "column", size ); String name = String.valueOf( model.getColumnCount() ); model.addColumn( name, newDatas ); } });
buttonRemRow.addActionListener( new ActionListener(){ public void actionPerformed(ActionEvent e) { int size = model.getRowCount(); int index = (int)(Math.random() * size); model.removeRow( index );
buttonRemRow.setEnabled( size > 1 ); } });
JFrame frame = new JFrame( "Demo" );
Container content = frame.getContentPane();
content.add( new JScrollPane( table ), BorderLayout.CENTER ); content.add( buttonAddRow, BorderLayout.NORTH ); content.add( buttonRemRow, BorderLayout.SOUTH ); content.add( buttonAddCol, BorderLayout.WEST );
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.pack(); frame.setVisible( true ); }
public static Vector createDataVector( String prefix, int size ){ Vector vector = new Vector( size ); for( int i = 0; i < size; i++ ) vector.add( prefix + " : " + size + " : " + i );
return vector; } } </code=java>
Was geschieht hier? Es gibt 3 JButtons, welche jeweils eine Row oder Column hinzufügen, oder eine Row entfernen. Die seltsame Schreibweise addActionListener( ... ) stellen anonyme Klassen dar.
Die eigentliche Hauptaktion macht das DefaultTableModel "model", alles andere dient nur dazu, dem Benutzer eine Möglichkeit zu bieten mit der Tabelle zu spielen.
4. Möglichkeit - TableModel
Mit Hilfe eines TableModels. Das TableModel ist ein Interface. Das JTable ruft über die Methode TableModel#getValueAt( int rowIndex, int columnIndex) für jede einzelne Zelle den Wert ab. Das muss nicht in einer bestimmten Reihenfolge geschehen, das JTable nimmt einfach gerade das, was es benötigt.
Das folgende kleine Beispiel zeigt: die Klasse Vehicel, die eigentlich überhaupt nichts mit einer Tabelle zu tun hat, kann mit Hilfe eines TableModels auf die JTable gebracht werden. Wichtig dabei ist: man muss nicht zuerst "von Hand" die Daten vorsortieren, sondern man gibt sie einfach dann zurück, wenn sie auch tatsächlich benötigt werden.
Was ist nun, falls die Daten veraendert wurden (z.B. eine Row hinzugefuegt)? Man muss die Methoden #addTableModelListener und #removeTableModelListener implementieren. Diese Methoden werden von verschiedenen Orten aufgerufen, zum Beispiel von JTable selbst, aber mögleicherweise auch vom Look And Feel. All diese Listener interessieren sich fuer Veränderungen des Models. Sobald sich die Daten ändern, muss man bei allen Listenern die Methode tableChanged( TableModelEvent event) aufrufen.
Das TableModelEvent enthält alle Informationen über die Veränderungen. Die Werte für das Event können nur über den Konstruktor gesetzt werden, ein paar einfache Beispiele aus der API: <code=java> // source Das TableModel, welches verändert wurde
TableModelEvent(source); // Sämtliche Daten von source wurden verändert TableModelEvent(source, TableModelEvent.HEADER_ROW); // Die Struktur der Daten wurde verändert, die Columns werden auf den Ursprungszustand gesetzt. TableModelEvent(source, 1); // Row 1 wurde verändert TableModelEvent(source, 3, 6); // Row 3, 4, 5, 6 wurden verändert TableModelEvent(source, 2, 2, 6); // Die Zelle (2, 6) wurde verändert TableModelEvent(source, 3, 6, TableModelEvent.ALL_COLUMNS, TableModelEvent.INSERT); // Es wurden neue Rows hinzugefügt, sie haben nun die Indices 3, 4, 5 und 6 TableModelEvent(source, 3, 6, TableModelEvent.ALL_COLUMNS, TableModelEvent.DELETE); // Die Rows mit den Indices 3, 4, 5 und 6 wurden gelöscht.
TableModelEvent(source, 2, 3, 5, TableModelEvent.INSERTED ) // Fehler: Es ist nicht möglich, dass Zelle (2,5) und (3,5) hinzugefügt werden. </code=java>
Sicher bekannt ist das Verhältnis zwischen Button und ActionListener. Sobald jemand den Button drückt, wird der ActionListener aufgerufen. Der Button ist sozusagen für die Aktion, der ActionListener für die Reaktion zuständig. Hier ist es genau umgekehrt: das TableModel startet die Aktion, die TableModelListener reagieren darauf.
Das folgende Beispiel zeigt, wie man dem JTable (indirekt) sagen kann, dass eine neue Row hinzugefügt wurde. Genau gleich wie hier das Hinzufügen von neuen Vehikeln, kann man auch das Löschen von einzelnen Columns, die Veränderung des Inhalts einer Zelle, etc... implementieren.
mehr zu Listeners.
Zuerst das Frame, wie wir es verwenden möchten: <code=java>import java.awt.BorderLayout; import java.awt.Container; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Vector;
import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.swing.table.TableModel;
public class JTableDemo{ public static void main( String[] args ){ // Unser TableModel (siehe unten) final Model model = new Model();
// Das JTable initialisieren JTable table = new JTable( model );
// Buttons, damit das alles schöner aussieht. final JButton buttonVehicel = new JButton( "add vehicel" );
// Den Buttons ein paar Reaktionen geben buttonVehicel.addActionListener( new ActionListener(){ public void actionPerformed(ActionEvent e) { // Die Anzahl Columns (Breite) der Tabelle int size = model.getRowCount();
// einen neuen Vector mit Daten herstellen Vehicel vehicel = createVehicel( size );
// ein neues Vehikel hinzufügen model.addVehicle( vehicel ); } });
JFrame frame = new JFrame( "Demo" );
Container content = frame.getContentPane();
content.add( new JScrollPane( table ), BorderLayout.CENTER ); content.add( buttonVehicel, BorderLayout.SOUTH );
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.pack(); frame.setVisible( true ); }
// Stellt einfach eine neue Instanz eines Vehikels her. public static Vehicel createVehicel( int index ){ index = index % 5; // Modulo
switch( index ){ case 0: return new Vehicel( "Fahrrad", 1, 2, false ); case 1: return new Vehicel( "Bus", 20, 4, true ); case 2: return new Vehicel( "Pferd", 1, 0, false ); case 3: return new Vehicel( "Zug", 1000, 80, true ); case 4: return new Vehicel( "Truck", 2, 10, true ); default: return null; }
} } </code=java> Der Code für eine Datenstruktur "Vehicel": <code=java>// Das Vehikel ist eine total unabhängige Klasse, die mit einer // Tabelle eigentlich gar nichts zu tun hat. class Vehicel{ private String name; private int places, wheels; private boolean motor;
public Vehicel( String name, int places, int wheels, boolean motor ){ this.name = name; this.places = places; this.wheels = wheels; this.motor = motor; }
public String getName(){ return name; } public int getPlaces(){ return places; } public int getWheels(){ return wheels; } public boolean hasMotor(){ return motor; } } </code=java> Das Model, dass die Vehicel in eine Form übersetzt, welche das JTable anzeigen kann: <code=java> // Unsere Implementation des TableModels class Model implements TableModel{ private Vector vehicels = new Vector(); private Vector listeners = new Vector();
public void addVehicle( Vehicel vehicel ){ // Das wird der Index des Vehikels werden int index = vehicels.size(); vehicels.add( vehicel );
// Jetzt werden alle Listeners benachrichtigt
// Zuerst ein Event, "neue Row an der Stelle index" herstellen TableModelEvent e = new TableModelEvent( this, index, index, TableModelEvent.ALL_COLUMNS, TableModelEvent.INSERT );
// Nun das Event verschicken for( int i = 0, n = listeners.size(); i<n; i++ ){ ((TableModelListener)listeners.get( i )).tableChanged( e ); } }
// Die Anzahl Columns public int getColumnCount() { return 4; }
// Die Anzahl Vehikel public int getRowCount() { return vehicels.size(); }
// Die Titel der einzelnen Columns public String getColumnName(int column) { switch( column ){ case 0: return "Name"; case 1: return "Fahrgäste"; case 2: return "Räder"; case 3: return "Besitzt Motor"; default: return null; } }
// Der Wert der Zelle (rowIndex, columnIndex) public Object getValueAt(int rowIndex, int columnIndex) { Vehicel vehicle = (Vehicel)vehicels.get( rowIndex );
switch( columnIndex ){ case 0: return vehicle.getName(); case 1: return new Integer( vehicle.getPlaces() ); case 2: return new Integer( vehicle.getWheels() ); case 3: return vehicle.hasMotor() ? Boolean.TRUE : Boolean.FALSE; default: return null; } }
// Eine Angabe, welchen Typ von Objekten in den Columns angezeigt werden soll public Class getColumnClass(int columnIndex) { switch( columnIndex ){ case 0: return String.class; case 1: return Integer.class; case 2: return Integer.class; case 3: return Boolean.class; default: return null; } }
public void addTableModelListener(TableModelListener l) { listeners.add( l ); } public void removeTableModelListener(TableModelListener l) { listeners.remove( l ); }
public boolean isCellEditable(int rowIndex, int columnIndex) {
return false;
}
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
// nicht beachten
}
}
</code=java>
API - TableModel
Ein Überblick aller Methoden, die das Interface TableModel vorschreibt.
int getColumnCount() Gibt die Anzahl Columns (Spalten) zurück. Dieser Wert muss natürlich >= 0 sein.
String getColumnName(int columnIndex) Gibt den Namen einer Column zurück. Der Name wird als Titel im JTableHeader verwendet.
int getRowCount() Gibt die Anzahl Rows (Zeilen) zurück. Wie der Wert bei "getColumnCount" muss dieser Wert >= 0 sein.
Object getValueAt(int rowIndex, int columnIndex) Gibt den Wert der Zelle (rowIndex, columnIndex) zurück. Dieses Object wird dem TableCellRenderer (siehe nächstes Kapitel) übergeben. Der Renderer ist eine Component, welche das Object irgendwie darstellen kann (z.B. ein JLabel das einen String als Text anzeigt.)
Primitive Datentypen wie int, double, ... werden am einfachsten in ihrere Wrapperklasse Integer, Double, ... zurückgegeben.
Das Object das hier zurückgegeben wird, muss eine Instanc von der Klasse sein, die bei #getColumnClass zurückgegeben wird.
Class getColumnClass(int columnIndex) Gibt die Klasse (oder Superklasse) aller Zellenwerte der Column columnIndex zurück. Das JTable verwendet diese Klasse, um herauszufinden, wie es diese Column am besten darstellt. z.B. Werden Icons anders dargestellt als Booleans...
z.B. die Klasse von String kann man so herausfinden: <code=java>Class string = String.class;</code=java> boolean isCellEditable(int rowIndex, int columnIndex) Gibt an, ob diese Zelle editiert werden kann. Falls ja, kann das JTable einen Editor (eine andere Component) in dieser Zelle darstellen, sollte der Benutzer sie selektieren (es gibt also immer höchstens einen sichtbaren Editor pro JTable). Wird das Editieren beendet (z.B. durch einen Druck auf ENTER), gibt der Editor ein Object zurueck, welches mit #setValueAt an das TableModel übertragen wird.
void setValueAt(Object aValue, int rowIndex, int columnIndex) Setzt an einer Stelle der Tabelle einen neuen Wert. Dieser neue Wert wird durch den Benutzer eingegeben. Diese Methode wird niemals aufgerufen, wenn #isCellEditable( rowIndex, columnIndex ) false zurueckgibt.
Sollte dem JTable nichts Spezielles gesagt worden sein, und gibt getColumnClass( columnIndex ) weder String, noch Boolean, Integer,..., oder Date zurück, wird aValue ein String sein. Dann muss das TableModel diesen String selbst übersetzen.
void addTableModelListener(TableModelListener l) Registriert einen TableModelListener bei diesem Model. Dieser Listener muss immer dann aufgerufen werden, sobald die Daten im Model verändert wurden (z.B. eine zusätzliche Row hineingeschoben wurde). Listener können vom JTable oder von anderen Klassen kommen, es muss das Model nicht interessieren.
void removeTableModelListener(TableModelListener l) Entfernt einen TableModelListener. z.B. muss das JTable ja nicht mehr über Veränderungen informiert werden, falls das Model vom Table entfernt wurde...
API - TableModelEvent
Das TableModelEvent befördert Informationen von einem TableModel zu seinen TableModelListenern.
Konstruktoren
Das TableModelEvent hat verschiedene Konstruktoren. Je nach dem welcher mit welchen Argumenten benutzt wurde, bekommt das Event eine andere Bedeutung.
source ist immer das TableModel, in dem etwas passiert ist.
TableModelEvent(TableModel source) Benutzen, falls die Veränderungen zu komplex für eine Beschreibung sind. Dann verliert das JTable allerdings viele Benutzereinstellungen, wie z.B. die Selektion.
TableModelEvent(TableModel source, int row) Gibt an, dass eine Row (mit dem Index row) verändert wurde. Allerdings kann row auch die Konstante HEADER_ROW sein, falls sich etwas mit der Titelleiste des JTables veraendert hat (z.B. ein Titel).
TableModelEvent(TableModel source, int firstRow, int lastRow) Gibt an, dass alle Rows von firstRow bis und mit lastRow verändert wurden.
TableModelEvent(TableModel source, int firstRow, int lastRow, int column) Gibt an, dass alle Rows von firstRow bis und mit lastRow verändert wurden. column kann der Index einer einzigen Column sein, oder aber die Konstante ALL_COLUMNS falls mehrere Columns verändert wurden.
TableModelEvent(TableModel source, int firstRow, int lastRow, int column, int type) Gibt an, dass alle Rows von firstRow bis und mit lastRow verändert wurden. column kann der Index einer einzigen Column sein, oder aber die Konstante ALL_COLUMNS falls mehrere Columns verändert wurden. type ist einer von INSERT, DELETE oder UPDATE.
Konstanten
static int ALL_COLUMNS Wird für das Argument column verwendet, und gibt an, dass alle Columns betroffen sind.
static int DELETE Wird für das Argument type verwendet, und gibt an, dass Rows gelöscht wurden.
static int HEADER_ROW Gibt an, dass die Titelleiste von irgendwelchen Veränderungen betroffen ist.
static int INSERT Wird für das Argument type verwendet, und gibt an, dass Rows eingefügt wurden.
static int UPDATE Wird für das Argument type verwendet, und gibt an, dass sich der Inhalt der Rows verändert hat.
Info - Aufruf von getValueAt
Es wurde ja schon gesagt: das JTable holt sich die Informationen in keiner bestimmten Reihenfolge. Das kann man sichtbar machen (aber Achtung, der Code nun folgt, sollte nicht so verwendet werden).
Die Tabelle <code=java>import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.event.TableModelListener; import javax.swing.table.TableModel;
public class JTableDemo{ public static void main( String[] args ){ // Unser TableModel (siehe unten) final Model model = new Model(50, 60);
// Das JTable initialisieren JTable table = new JTable( model );
JFrame frame = new JFrame( "Demo" ); frame.getContentPane().add( new JScrollPane( table ) );
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.pack(); frame.setVisible( true ); } } </code=java> Ein TableModel das Integer ausgiebt, und mitzählt, wieoft "getValueAt" aufgerufen wurde (für jede Zelle einzeln) <code=java>class Model implements TableModel{ private int width, height;
int[][] calls;
public Model( int width, int height ){ this.width = width; this.height = height; calls = new int[ width ][ height ]; }
// Die Anzahl Columns public int getColumnCount() { return width; }
// Die Anzahl Vehikel public int getRowCount() { return height; }
// Die Titel der einzelnen Columns public String getColumnName(int column) { return String.valueOf( column ); }
// Der Wert der Zelle (rowIndex, columnIndex) public Object getValueAt(int rowIndex, int columnIndex) { // bei jedem Aufruf wird dieser Wert um 1 erhöht return new Integer( calls[ columnIndex ][ rowIndex ]++ ); }
// Eine Angabe, welchen Typ von Objekten in den Columns angezeigt werden soll public Class getColumnClass(int columnIndex) { return Integer.class; }
public void addTableModelListener(TableModelListener l) {} public void removeTableModelListener(TableModelListener l) {} public boolean isCellEditable(int rowIndex, int columnIndex) { return false; } public void setValueAt(Object aValue, int rowIndex, int columnIndex) {} } </code=java> Dieses TableModel zählt für jede Zelle, wie oft sie aufgerufen wurde.
Ausblick - Eingeben von Daten
Es wäre doch schön, wenn der Benutzer auch Daten eingeben könnte. Die JTable und das TableModel sind bereits darauf vorbereitet, und es ist nicht viel vonnöten, bis das Beispiel aus 4. veränderbare Vehikel besitzt:
Das Vehicel benötigt noch neue Methoden: <code=java>class Vehicel{ ... // der ganze Rest
// Diese 4 Methoden müssen dem Vehikel noch hinzugefügt werden: public void setName( String name ){ this.name = name; } public void setPlaces( int places ){ this.places = places; } public void setWheels( int wheels ){ this.wheels = wheels; } public void setHasMotor( boolean motor ){ this.motor = motor; } } </code=java> Die letzen beiden Methoden von "Model" müssen angepasst werden, so dass auch Daten gesetzt werden können <code=java>class Model implements TableModel{
... // der ganze Rest
// Die beiden letzten Methoden (siehe 4.) müssen noch verändert werden.
// Gibt nun an, dass jede Zelle editierbar ist. public boolean isCellEditable(int rowIndex, int columnIndex) { return true; }
// Wird von der JTable aufgerufen, falls in eine Zelle ein neuer Wert gesetzt werden soll // Praktischerweise ist "aValue" ein Objekt desselben Types, welchen wir bei // "public Class getColumnClass(int columnIndex)" zurückgegeben haben (Also String, Integer oder Boolean) public void setValueAt(Object aValue, int rowIndex, int columnIndex) { Vehicel vehicel = (Vehicel)vehicels.get( rowIndex );
switch( columnIndex ){ case 0: vehicel.setName( (String)aValue ); break; case 1: vehicel.setPlaces( ((Integer)aValue).intValue() ); break; case 2: vehicel.setWheels( ((Integer)aValue).intValue() ); break; case 3: vehicel.setHasMotor( ((Boolean)aValue).booleanValue() ); break; } } } </code=java>
Teil 3 - Breite der Spalten
Die Breite der Spalten lässt sich über die Objekte der Klasse TableColumn verändern. Dabei benutzt man nicht die Methode setWidth, sondern die Methode setPreferredWidth. Natürlich kann man die Breite der Spalten erst festlegen, nachdem man das Model mit der Anzahl Spalten übergeben hat.
1. Möglichkeit - Direkt
Man kann die TableColumns über ihren Titel abrufen. <code=java>import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTable;
public class JTableDemo{
public static void main( String[] args ){ // Die Daten für das Table String[][] data = new String[][]{ {"a", "b", "c", "d"}, {"e", "f", "g", "h"}, {"i", "j", "k", "l"} }; // Die Column-Titles String[] title = new String[]{ "A", "B", "C", "D" }; // Das JTable initialisieren JTable table = new JTable( data, title ); // Das automatische Neusetzen der Grösse würde das Vorhaben, die Grösse selbst // zu setzen, stören. table.setAutoResizeMode( JTable.AUTO_RESIZE_OFF ); table.getColumn( "A" ).setPreferredWidth( 20 ); table.getColumn( "B" ).setPreferredWidth( 50 ); table.getColumn( "C" ).setPreferredWidth( 80 ); table.getColumn( "D" ).setPreferredWidth( 110 ); JFrame frame = new JFrame( "Demo" ); frame.getContentPane().add( new JScrollPane( table ) ); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.pack(); frame.setVisible( true ); }
2. Möglichkeit - Mit Indices über das TableColumnModel
Nicht immer gibt es ein eindeutiges Objekt, welches eine Column identifiziert. Ein simples Beispiel: es wird zweimal derselbe Titel gewählt, oder die Titel sind dynamisch, und man weiss nicht, wann welcher Titel angezeigt wird.
In diesem Fall muss man tiefer in das JTable hineinlangen, um die Columns zu erreichen. Man muss das TableColumnModel befragen. <code=java>import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.table.DefaultTableColumnModel; import javax.swing.table.TableColumnModel;
public class JTableDemo{
public static void main( String[] args ){ // Die Daten für das Table String[][] data = new String[][]{ {"a", "b", "c", "d"}, {"e", "f", "g", "h"}, {"i", "j", "k", "l"} }; // Die Column-Titles String[] title = new String[]{ "A", "A", "A", "A" }; // Das JTable initialisieren JTable table = new JTable( data, title ); // Das automatische Neusetzen der Grösse würde das Vorhaben, die Grösse selbst // zu setzen, stören. table.setAutoResizeMode( JTable.AUTO_RESIZE_OFF ); // Über das TableColumnModel kommt man an die wichtigen Daten TableColumnModel columnModel = table.getColumnModel(); // Die einzelnen Columns ansprechen und die Grösse setzen columnModel.getColumn( 0 ).setPreferredWidth( 20 ); columnModel.getColumn( 1 ).setPreferredWidth( 50 ); columnModel.getColumn( 2 ).setPreferredWidth( 80 ); columnModel.getColumn( 3 ).setPreferredWidth( 110 ); JFrame frame = new JFrame( "Demo" ); frame.getContentPane().add( new JScrollPane( table ) ); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.pack(); frame.setVisible( true ); }
3. Der Auto-Resize-Modus
In den beiden vorangegangen Beispielen wurd der Autoresize-Modus deaktiviert. <code=java>table.setAutoResizeMode( JTable.AUTO_RESIZE_OFF );</code=java> Wenn man diese Zeile weglässt, versucht das JTable die Grösse der Spalten anzupassen. Dabei benutzt es die Einstellungen der "preferredWidth", welche man ja verändert hat:
Zum Vergleich, mit deaktiviertem Resize-Mode: Datei:JTableTutorial10.png