FAQ-Beitrag, in Bearbeitung

Status
Nicht offen für weitere Antworten.
B

Beni

Gast
Ich hab vor einen FAQ-Beitrag über das berühmt, berüchtigte JTable zu schreiben.
Wer Lust hat mitzumachen: Beiträge aller Art werden gerne angenommen


Teil 1 - Tutorials, API ... frei
Teil 2 - TableModel ... in Bearbeitung
Teil 3 - TableCellRenderer ... in Bearbeitung
Teil 4 - TableCellEditor ... in Bearbeitung
Teil 5 - JTableHeader ... in Bearbeitung - von Roar
Teil 6 - ??


JTable - 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:
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
		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 );
	}
}

table_01.png


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:
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 );
	}
}

table_02.png


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:
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;
	}
}

table_03.png


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:
// 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.

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:
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;	
		}
		
	}
}

Der Code für eine Datenstruktur "Vehicel"
Code:
// 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; }
}

Das Model, dass die Vehicel in eine Form übersetzt, welche das JTable anzeigen kann
Code:
// 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
	}
}

table_04.png


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) zurueck.
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 zurueck. 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:
Class string = String.class;

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:
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 );
	}
}

Ein TableModel das Integer ausgiebt, und mitzählt, wieoft "getValueAt" aufgerufen wurde (für jede Zelle einzeln)
Code:
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) {}
}

table_06.png


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:
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; }
}

Die letzen beiden Methoden von "Model" müssen angepasst werden, so dass auch Daten gesetzt werden können
Code:
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;
		}
	}
}

table_05.png


Um das ganze zu verstehen ist es wohl am einfachsten, wenn Du mal den Quellcode kopierst (es reicht eine einzige Datei namens "TableDemo.java"), und versuchst was eigenes hinzuzufügen.
 

Maks

Aktives Mitglied
*Beifall!*

wunderbar das Ganze, leider kenn ich mich in dem Bereich gar nicht aus, aber solche Anleitungen liebe ich, mache ich in der Firma auch immer, weil ich mir dann nie wieder die Sachen zusammensuchen muß.

Sobald ich kann, schreib ich die "Gesammelten Werke" über ein anderes Thema weiter.
Wenn das jeder machen würde, gäbs theoretisch nach ein par Jahren vielleicht mal ein Buch...
Denn EIN Thema bei dem man sich besonders gut auskennt, hat bestimmt jeder!
 

Reality

Top Contributor
Hi Beni!
Hoffe das gilt jetzt nicht als Spam.
Ab welcher Java-Version wird das unterstützt?!

Liebe Grüße
Reality
 
B

Beni

Gast
???:L hm, also ab 1.4 sicher...
Aber ich hab k.A. wie's früher aussieht. (Naja, das JTable ist ja auch schon ein Zeitchen dabei, aber das MS-Explorer-Applet-JRE-1.1.xx ist wohl zu alt)
 
B

Beni

Gast
JTable - Teil 3 - Darstellung der Daten

Bereits in im vorhergehenden Teil hat man gesehen, dass das JTable offenbar mehr kann als nur Text zu zeichnen:
table_04.png


1. Das Standartverhalten
Mit Hilfe der Methode TableModel#getColumnClass versucht das JTable herauszufinden, wie diese Column am besten dargestellt werden kann.

Ein Blick in den Quellcode von JTable verrät, dass Number, Float, Double, Date, Icon, ImageIcon und Boolean eine Spezialbehandlung erhalten.

Sollte ein Object nicht dazu passen, wird die "toString" Methode benutzt.

Demonstrieren wir mal, wie das aussieht:
Code:
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.util.Date;

import javax.swing.Icon;
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 ){
      // Unser TableModel (siehe unten)
      Model model = new Model();
      
      // 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 );
   }
}

Ein einfaches Icon, damit man nicht Bilder laden, etc... muss
Code:
class DemoIcon implements Icon{
	public int getIconHeight() {
		return 15;
	}
	public int getIconWidth() {
		return 15;
	}
	public void paintIcon(Component c, Graphics g, int x, int y) {
		g.setColor( Color.DARK_GRAY );
		g.fillOval( x, y, 10, 10 );
		g.setColor( Color.GREEN );
		g.fillOval( x+5, y+5, 10, 10 );
	}
}

Wiedereinmal ein TableModel. Diesmal wird AbstractTableModel als Parent-Klasse benutzt, denn so muss man weniger Methoden implementieren
Code:
class Model extends AbstractTableModel{
	private Object[] objects;
	private Class[] classes;
	
   	public Model(){
   		objects = new Object[]{
				"Text",
				new Float( 5.123f ),
				new Boolean( true ),
				new Date( System.currentTimeMillis() ),
				new DemoIcon(),
				Color.RED
   		};
   		
   		classes = new Class[]{
				String.class,
				Float.class,
				Boolean.class,
				Date.class,
				Icon.class,
				Color.class
   		};
   	}
   
   	// Die Anzahl Columns
   	public int getColumnCount() {
   		return objects.length;
   	}
   
   	// Die Anzahl Rows
   	public int getRowCount() {
   		return 1;
   	}
   
   	// Die Titel der einzelnen Columns (Der Name der Klasse die Dargestellt wird)
   	public String getColumnName(int column) {
   		return getColumnClass( column ).getName();
   	}
   
   	// Der Wert der Zelle (rowIndex, columnIndex)
   	public Object getValueAt(int rowIndex, int columnIndex) {
   		return objects[ columnIndex ];
   	}

   	// Eine Angabe, welchen Typ von Objekten in den Columns angezeigt werden soll
   	public Class getColumnClass(int columnIndex) {
   		return classes[columnIndex];
   	}
}

table_07.png


Wie man sieht, wurde das Color-Objekt nicht gerade schön dargestellt. Da das JTable nicht wusste, wie es mit einem Color-Objekt umgehen soll, wurde einfach die "toString"-Methode benutzt.

2. Ein einfacher TableCellRenderer
Wie kann man dem JTable also nun sagen, dass Color auch speziell angezeigt werden soll?
Indem man einen TableCellRenderer implementiert, und dem JTable über die Methode JTable#setDefaultRenderer übergibt.

Der Einfachheit halber, wird für das Beispiel ein DefaultTableCellRenderer genommen, und ein bisschen verändert.

Die bekannte Tabelle mit einem zusätzlichen Befehl
Code:
public class JTableDemo{
   public static void main( String[] args ){
      // Unser TableModel (siehe unten)
      Model model = new Model();
      
      // Das JTable initialisieren
      JTable table = new JTable( model );
      
      // Hier wird der JTable gesagt, dass Objekte des Types "Color" vom "ColorTableCellRenderer" dargestellt werden
      table.setDefaultRenderer( Color.class, new ColorTableCellRenderer() );
      
      JFrame frame = new JFrame( "Demo" );
      frame.getContentPane().add( new JScrollPane( table ) );
      
      frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
      frame.pack();
      frame.setVisible( true );
   }
}

Das DemoIcon bleibt unverändert
Code:
class DemoIcon implements Icon{
   ...
}

Auch an "Model" muss nichts verändert werden
Code:
class Model extends AbstractTableModel{
   ...
}

Und jetzt kommt der neue TableCellRenderer.
Code:
class ColorTableCellRenderer extends DefaultTableCellRenderer{
	public Component getTableCellRendererComponent(JTable table, Object value,
			boolean isSelected, boolean hasFocus, int row, int column) {
		super.getTableCellRendererComponent(table, value, isSelected,
				hasFocus, row, column);
		
		Color color = (Color)value;
		
		setText( color.getRed() + ", " + color.getBlue() + ", " + color.getGreen() );
		setForeground( color );
		
		return this;
	}
}

table_08.png


Das JTable erwartet von einem TableCellRenderer eine Component, welche genau eine Zelle darstellt. Allerdings wird diese Component nicht für immer behalten, sondern sie muss nur kurz diese Zelle zeichnen, und wird danach nicht mehr benötigt.

Was macht "ColorTableCellRenderer"?
Sobald das JTable vom ColorTableCellRenderer eine Componen haben möchte, ruft es ersteinmal die entsprechende Methode von DefaultTableCellRenderer auf. Dort werden Hintergrundfarbe und Schriftart gesetzt.
Danach setzt ColorTableCellRenderer einen Text der zu der Farbe passt (die RGB-Werte), und verändert die Schriftfarbe.
Und dann gibt er sich selbst zurück, denn betrachtet man die API, so sieht man, dass DefaultTableModel eine Unterklasse von JLabel (und damit auch von Component) ist, und dies deshalb ohne Probleme geht.

Wichtig ist: es muss nie eine neue Component hergestellt werden, das Schlüsselwort new hat hier nichts verloren.

3. Alles zum gleichen TableCellRenderer
Manchmal möchte man vielleicht mehr Kontrolle über die Art, wie die Zellen dargestellt werden.
Eine einfache Variante ist folgende: Das TableModel gibt für alle ColumnClasses an, sie seien vom Typ Object. Und für den Typ Object setzt man einen eigenen TableCellRenderer.

Die Tabelle mit anderem TableCellRenderer
Code:
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.util.Date;

import javax.swing.Icon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;

public class JTableDemo{
   public static void main( String[] args ){
      // Unser TableModel (siehe unten)
      Model model = new Model();
      
      // Das JTable initialisieren
      JTable table = new JTable( model );

      // Den Spezial-CellRenderer übergeben.
      table.setDefaultRenderer( Object.class, new Renderer() );
            
      JFrame frame = new JFrame( "Demo" );
      frame.getContentPane().add( new JScrollPane( table ) );
      
      frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
      frame.pack();
      frame.setVisible( true );
   }
}

Das DemoIcon bekommt noch die Möglichkeit, andere Farben zu benutzen
Code:
class DemoIcon implements Icon{
	private Color top, bottom;
	
	public DemoIcon( Color top, Color bottom ){
		this.top = top;
		this.bottom = bottom;
	}
	
	public int getIconHeight() {
		return 15;
	}
	public int getIconWidth() {
		return 15;
	}
	public void paintIcon(Component c, Graphics g, int x, int y) {
		g.setColor( top );
		g.fillOval( x, y, 10, 10 );
		g.setColor( bottom );
		g.fillOval( x+5, y+5, 10, 10 );
	}
}

Das TableModel gibt nun für jede Column an, dass Object's angezeigt werden
Code:
class Model extends AbstractTableModel{
	private Object[][] objects;
	
   	public Model(){
   		objects = new Object[][]{
   				{"Text",
				new Float( 5.123f ),
				new Boolean( true ),
				new Date( System.currentTimeMillis() ),
				new DemoIcon( Color.DARK_GRAY, Color.GREEN ),
				Color.RED},
				
   				{"Hallo",
				new Double( 531.45 ),
				new Boolean( false ),
				new Date( System.currentTimeMillis() + 50000000 ),
				new DemoIcon( Color.BLUE, Color.GRAY ),
				Color.CYAN},
				
	   			{"Tschau",
				new Integer( 123 ),
				new Boolean( true ),
				"unbekannt",
				new DemoIcon( Color.LIGHT_GRAY, Color.MAGENTA ),
				Color.PINK}	
   		};
   	}
   
   	// Die Anzahl Columns
   	public int getColumnCount() {
   		return 6;
   	}
   
   	// Die Anzahl Rows
   	public int getRowCount() {
   		return 3;
   	}
   
   	// 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) {
   		return objects[ rowIndex ][ columnIndex ];
   	}

   	// Eine Angabe, welchen Typ von Objekten in den Columns angezeigt werden soll
   	public Class getColumnClass(int columnIndex) {
   		return Object.class;
   	}
}

Der neue TableCellRenderer entscheidet für jede Zelle einzeln, wie sie dargestellt werden soll.
Code:
class Renderer extends JLabel implements TableCellRenderer{
	private Color colorSelected = new Color( 200, 255, 200 );
	private Color colorFocus = new Color( 255, 200, 200 );
	private Color colorNormal = new Color( 200, 200, 255 );

	public Renderer(){
		setOpaque( true );
	}
	
	public Component getTableCellRendererComponent(JTable table, Object value,
			boolean isSelected, boolean hasFocus, int row, int column) {
		
		// die normalen Farben
		setForeground( Color.BLACK );
		if( hasFocus )
			setBackground( colorFocus );
		else if( isSelected )
			setBackground( colorSelected );
		else
			setBackground( colorNormal );
		
		setText( null );
		setIcon( null );
		
		if( value instanceof Date )
			setText( ((Date)value).toGMTString() );
		else if( value instanceof Icon )
			setIcon( (Icon)value );
		else if( value instanceof Color ){
			Color color = (Color)value;
			setForeground( color );
			setText( color.getRed() + ", " + color.getGreen() + ", " + color.getBlue() );
		}
		else if( value instanceof Boolean ){
			if( ((Boolean)value).booleanValue() )
				setText( "yes" );
			else
				setText( "no" );
		}
		else
			setText( value.toString() );
		
		return this;
	}
}

table_09.png


Man beachte: in Column 3, ganz unten, ist ein String, und kein Date-Object. Da der Renderer für jede Zelle einzeln entscheiden kann, gibt es hier keine Probleme.
 
B

Beni

Gast
JTable - Teil 4 - Verändern von Daten

Am Ende des 2. Teils wurde gezeigt, dass man auch Daten eingeben kann. Jetzt soll das nocheinmal vertieft behandelt werden.

1. Der einfache Weg

Das JTable unterstützt von sich aus das Editieren von Zahlen und von Booleans. Alles andere wird wie ein String behandelt, d.h. da der Benutzer nur einen Text eingeben kann, wird dem Model ein String übergeben, welches es selbst in ein Objekt übersetzen muss.

Die Verwendung der Table-eigenen Editoren ist einfach:

Zuerst die Tabelle
Code:
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 ){
      // Unser TableModel (siehe unten)
      Model model = new Model();
      
      // 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 );
   }
}

Jetzt das Model. Jede Zelle ist editierbar, doch jede Column wird anders behandelt.
Code:
class Model extends AbstractTableModel{
	private String[] strings;
	private int[] integers;
	private double[] doubles;
	private boolean[] booleans;
	
   	public Model(){
   		strings = new String[]{ "hallo", "ich", "bin", "eine", "Demo" };
   		integers = new int[]{ 1, 2, 3, 4, 5 };
   		doubles = new double[]{ 1.2, 3.4, 5.6, 7.8, 9.11 };
   		booleans = new boolean[]{ true, false, false, true, true };
   	}
   
   	// Die Anzahl Columns
   	public int getColumnCount() {
   		return 4;
   	}
   
   	// Die Anzahl Rows
   	public int getRowCount() {
   		return 5;
   	}
   
   	// Die Titel der einzelnen Columns
   	public String getColumnName(int column) {
   		switch( column ){
   			case 0: return "String";
   			case 1: return "Integer";
   			case 2: return "Double";
   			case 3: return "Boolean";
   			default: return null;
   		}
   	}
   
   	// Der Wert der Zelle (rowIndex, columnIndex)
   	public Object getValueAt(int rowIndex, int columnIndex) {
   		switch( columnIndex ){
   			case 0: return strings[ rowIndex ];
   			case 1: return new Integer( integers[ rowIndex ] );
   			case 2: return new Double( doubles[ rowIndex ] );
   			case 3: return new Boolean( booleans[ rowIndex ] );
   			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 Double.class;
   			case 3: return Boolean.class;
   			default: return null;
   		}
   	}
   	
   	// Jede Zelle ist editierbar
	public boolean isCellEditable(int rowIndex, int columnIndex) {
		return true;
	}
	
	// Wird aufgerufen, falls der Wert einer Zelle verändert wurde
	public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
		switch( columnIndex ){
			case 0:
				strings[ rowIndex ] = aValue.toString();
				break;
			case 1:
				integers[ rowIndex ] = ((Integer)aValue).intValue();
				break;
			case 2:
				doubles[ rowIndex ] = ((Double)aValue).doubleValue();
				break;
			case 3:
				booleans[ rowIndex ] = ((Boolean)aValue).booleanValue();
		}
	}
}

table_10.png


Es wichtig, dass man nicht vergisst, dass dies ausschliesslich für Strings (oder Objekte die als String dargestellt werden können, z.B. ein Datum), Nummern und Booleans so funktioniert!

2. Mit Hilfe eines TableCellEditors
Man kann eigene Editoren schreiben. Dabei kann ähnlich wie beim CellRenderer eine beliebige Component benutzt werden.

Schon in Teil 3 wurde gezeigt, dass das JTable Objekte des Types "Date" nicht richtig unterstützt. Das soll jetzt geändert werden.

Die Tabelle, man beachte, dass ein "DateRenderer" und ein "DateEditor" registriert werden.
Code:
import java.awt.Component;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.EventObject;
import java.util.Vector;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JTable;
import javax.swing.SpinnerDateModel;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellEditor;

public class JTableDemo{
   public static void main( String[] args ){
      // Unser TableModel (siehe unten)
      Model model = new Model();
      
      // Das JTable initialisieren
      JTable table = new JTable( model );
      
      // Renderer und Editor für "Date" werden registriert
      table.setDefaultRenderer( Date.class, new DateRenderer() );
      table.setDefaultEditor( Date.class, new DateEditor() );
            
      JFrame frame = new JFrame( "Demo" );
      frame.getContentPane().add( new JScrollPane( table ) );
      
      frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
      frame.pack();
      frame.setVisible( true );
   }
}

Die Tabelle soll was sinnvolles darstellen: Notizen die aus einem Datum und aus einem Ereignis bestehen
Code:
// Eine einfache Klasse die Notizen beschreibt
class Note{
	private Date date;
	private String event;
	
	public Note( String event, Date date ){
		setEvent( event );
		setDate( date );
	}
	
	public String getEvent(){ return event; }
	public Date getDate(){ return date; }
	
	public void setEvent( String event ){ this.event = event; }
	public void setDate( Date date ){ this.date = date; }
}

Das TableModel, man beachte, dass es keine Verbindungen zum Renderer oder zum Editor besitzt! Allerdings kann durch das Wissen, dass "Date" nun unterstützt wird, in der Methode "setValueAt" ein Cast "Object -> Date" durchgeführt werden.
Code:
class Model extends AbstractTableModel{
	private Note[] notes;
	
   	public Model(){
   		notes = new Note[]{
   				new Note( "Mit Hund rausgehen", new Date(  104, 5, 2, 14, 0 ) ),
				new Note( "Kino", new Date( 104, 5, 7, 21, 30 ) ),
				new Note( "Abschlussprüfung", new Date( 104, 8, 14, 7, 0 ) ),
				new Note( "Einkaufen gehen", new Date( 104, 5, 2, 16, 0 ) ),
				new Note( "Rechnung bezahlen", new Date( 110, 11, 30, 23, 59 ) ),
   		};
   	}
   
   	// Die Anzahl Columns
   	public int getColumnCount() {
   		return 2;
   	}
   
   	// Die Anzahl Rows
   	public int getRowCount() {
   		return 5;
   	}
   
   	// Die Titel der einzelnen Columns
   	public String getColumnName(int column) {
   		if( column == 0 )
   			return "Ereignis";
   		else
   			return "Datum";
   	}
   
   	// Der Wert der Zelle (rowIndex, columnIndex)
   	public Object getValueAt(int rowIndex, int columnIndex) {
   		if( columnIndex == 0 )
   			return notes[ rowIndex ].getEvent();
   		else
   			return notes[ rowIndex ].getDate();
   	}

   	// Eine Angabe, welchen Typ von Objekten in den Columns angezeigt werden soll
   	public Class getColumnClass(int columnIndex) {
   		if( columnIndex == 0 )
   			return String.class;
   		else
   			return Date.class;
   	}
   	
   	// Jede Zelle ist editierbar
	public boolean isCellEditable(int rowIndex, int columnIndex) {
		return true;
	}
	
	// Wird aufgerufen, falls der Wert einer Zelle verändert wurde
	// Der Wert kommt entweder als String, oder von dem 
	// DateEditor (und daher als Date). 
	public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
		if( columnIndex == 0 )
			notes[ rowIndex ].setEvent( (String)aValue );
		else
			notes[ rowIndex ].setDate( (Date)aValue );
	}
}

Der Renderer, der "Date" auf eine brauchbare Art darstellt ist einfach gehalten:
Code:
class DateRenderer extends DefaultTableCellRenderer{
	private DateFormat format; 
	
	public DateRenderer(){
		format = DateFormat.getDateTimeInstance();
	}
	
	public Component getTableCellRendererComponent(JTable table, Object value,
			boolean isSelected, boolean hasFocus, int row, int column) {

		// Date's werden auf einem einfache Weg in lesbare Texte umgewandelt.
		String text = format.format( (Date)value );
		
		return super.getTableCellRendererComponent(table, text, isSelected,
				hasFocus, row, column);
	}
}

Der Editor. Der Editor erbt von JSpinner. Glücklicherweise unterstützt der JSpinner "Date", und so kann man das meiste an den JSpinner delegieren
Code:
class DateEditor extends JSpinner implements TableCellEditor{
	private SpinnerDateModel model;
	
	private Vector listeners = new Vector();
	
	// Standartkonstruktor
	public DateEditor(){
		model = new SpinnerDateModel();
		model.setCalendarField( Calendar.DAY_OF_YEAR );
		setModel( model );
	}
	
	// Möglicherweise möchte jemand über Ereignisse des Editors
	// informiert werden
	public void addCellEditorListener(CellEditorListener l) {
		listeners.add( l );
	}
	
	// Ein CellEditorListener entfernen
	public void removeCellEditorListener(CellEditorListener l) {
		listeners.remove( l );
	}
	
	// Gibt den aktuellen Wert des Editors zurück.
	public Object getCellEditorValue() {
		return model.getDate();
	}
	
	// Gibt eine Component zurück, welche auf dem JTable dargestellt wird,
	// und mit der der Benutzer interagieren kann.
	public Component getTableCellEditorComponent(JTable table, Object value,
			boolean isSelected, int row, int column) {
		
		model.setValue( value );
		return this;
	}
	
	// Gibt an, ob die Zelle editierbar ist. Das EventObject kann
	// ein MouseEvent, ein KeyEvent oder sonst was sein.
	public boolean isCellEditable(EventObject anEvent) {
		return true;
	}
	
	// Gibt an, ob die Editor-Component selektiert werden muss, um
	// sie zu benutzen. Diese Editor soll immer selektiert werden,
	// deshalb wird hier true zurückgegeben
	public boolean shouldSelectCell(EventObject anEvent) {
		return true;
	}
	
	// Bricht das editieren der Zelle ab
	public void cancelCellEditing() {
		fireEditingCanceld();
	}
	
	// Stoppt das editieren der Zelle, sofern möglich.
	// Da der JSpinner immer einen gültigen Wert anzeigt, kann auch 
	// jederzeit gestoppt werden (return-Wert = true)
	public boolean stopCellEditing() {
		fireEditingStopped();
		return true;
	}
	
	// Benachrichtig alle Listener, dass das Editieren abgebrochen wurde
	protected void fireEditingCanceld(){
		ChangeEvent e = new ChangeEvent( this );
		for( int i = 0, n = listeners.size(); i<n; i++ )
			((CellEditorListener)listeners.get( i )).editingCanceled( e );
	}
	
	// Benachrichtig alle Listener, dass das Editieren beendet wurde
	protected void fireEditingStopped(){
		ChangeEvent e = new ChangeEvent( this );
		for( int i = 0, n = listeners.size(); i<n; i++ )
			((CellEditorListener)listeners.get( i )).editingStopped( e );
	}
}

table_11.png


Also: der Editor ist eine Component die auf das JTable gesetzt wird, einen bestimmten Wert zugewiesen bekommt, und diesen Wert verändern darf. Klickt der Benutzer an eine andere Stelle, wird das Editieren entweder abgebrochen (cancelCellEditing) oder beendet (stopCellEditing), und der Wert dem TableModel über die Methode setValueAt überwiesen.
 
R

Roar

Gast
JTable - Teil 5 - JTableHeader, TableColumn und das TableColumnModel

Der JTableHeader ist ein Component, der die Darstellung der Tabellenspalten einer JTable übernimmt. Meistens braucht an diesen Component nicht, da JTable ihn standartmäßig mitinstantiiert. Die beiden Methoden getTableHeader() und setTableHeader() in JTable bieten an mit dem JTableHeader zu arbeiten. JTableHeader ist nicht dazu da um Spalten hinzuzufügen oder zu entfernen, sondern nur für die Darstellung zuständig. Für die Daten der Spalten ist das TableColumnModel zuständig, welches man wahlweise der JTable oder dem JTableHeader übergeben kann.

JTableHeader, in javax.swing.table, bietet einige interessante Methode an um die Spalten zu manipulieren. Die gebräuchlichsten beiden Methoden sind, finde ich, setReorderingAllowed(boolean reorderingAllowed) und setResizingAllowed(boolean resizingAllowed). Sie sollten selbsterklärend sein. Hier ein kleiens Beispielprogramm:

Code:
import javax.swing.*;
import javax.swing.table.JTableHeader;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableColumn;



class TableHeaderTest {
	
	public static void main(String[] args) {
		// JTable initialisieren und mit Inhalt füllen; siehe dazu Teil 2
		JTable table = new JTable(new String[][]{{"1", "2", "3"},{"4", "5", "6"}}, new String[]{"A", "B", "C"});
		
		// JTableHeader holen
		JTableHeader header = table.getTableHeader();
		
		// ColumnModel holen:
		TableColumnModel columnModel = header.getColumnModel();
		
		// TableColumn erstellen
		TableColumn aColumn = new TableColumn();
		// setHeaderValue() setzt den Titel
		aColumn.setHeaderValue("D");
		// modelIndex zeigt an von welcher Spalte im DatenModel die neue Spalte ihre Werte holen soll
		aColumn.setModelIndex(1); // Index 1 im Model sind also "2" und "5"
		columnModel.addColumn(aColumn);
		
		// eine spalte verschieben
		columnModel.moveColumn(0, 2); // spalte 0 (mit Tiel "A") verschieben an position 2 im model
		
		// spalten nicht resizable machen, nicht zulassen dass die spaltenreihenfolge geändert werden kann
		header.setResizingAllowed(false);
		header.setReorderingAllowed(false);
		
		// JFrame konstruieren
		JFrame frame = new JFrame("JTableHeader test");
		// Table in JScrollPane einfügen und dem Frame hinzufügen
		frame.getContentPane().add(new JScrollPane(table));
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.pack();
		frame.setVisible(true);
	}
}

Ausgabe:

JTableTest1.jpg


Erklärung:
Zuerst wird die Tabelle ganz normal angelegt, dann holt sich das Programm den JTableHeader der Tabelle. Vom TableHeader das DolumnModel, was aber auch direkt über die JTable gehen würde. Dann wird eine Spalte hinzugefügt, mit dem Model Index 1 (Das TableModel ist in Teil 2 erklärt). Danach wird die Spalte an position 0 ("A") vershcoben an position 2. Man muss immer daran denken dass das Zählen bei 0 anfängt, also sind mit den Zahlen immer die Positionen im Model gemeint. Letztendlich wird noch verboten die Spaltengröße, und die Spaltenreihenfolge zu ändern.

Eine weitere Möglichkeit von JTableHeader und TableColumn ist, wie auch bei JTable möglich, den Renderer für die Spalte zu setzen.
Es wird das gleiche Renderer Interface benutzt wie bei der JTable: javax.swing.table.TableCellRenderer. Zugewiesen wird der Renderer mit setDefaultRenderer(TableCellRenderer defaultRenderer);

Beispiel:
Code:
import java.awt.Component;
import java.awt.Color;
import javax.swing.*;
import javax.swing.table.JTableHeader;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableCellRenderer;



class TableHeaderTest {
	
	public static void main(String[] args) {
		// JTable initialisieren und mit Inhalt füllen; siehe dazu Teil 2
		JTable table = new JTable(new String[][]{{"1", "2", "3"},{"4", "5", "6"}}, new String[]{"A", "B", "C"});
		
		// JTableHeader holen
		JTableHeader header = table.getTableHeader();
		
		// ColumnModel holen:
		TableColumnModel columnModel = header.getColumnModel();
		
		// TableColumn erstellen
		TableColumn aColumn = new TableColumn();
		// setHeaderValue() setzt den Titel
		aColumn.setHeaderValue("D");
		// modelIndex zeigt an von welcher Spalte im DatenModel die neue Spalte ihre Werte holen soll
		aColumn.setModelIndex(1); // Index 1 im Model sind also "2" und "5"
		columnModel.addColumn(aColumn);
		
		// eine spalte verschieben
		columnModel.moveColumn(0, 2); // spalte 0 (mit Tiel "A") verschieben an position 2 im model
		
		// spalten nicht resizable machen, nicht zulassen dass die spaltenreihenfolge geändert werden kann
		header.setResizingAllowed(false);
		header.setReorderingAllowed(false);
		
		// Renderer zuweisen
		header.setDefaultRenderer(new MyHeaderCellRenderer());
		
		// JFrame konstruieren
		JFrame frame = new JFrame("JTableHeader test");
		// Table in JScrollPane einfügen und dem Frame hinzufügen
		frame.getContentPane().add(new JScrollPane(table));
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.pack();
		frame.setVisible(true);
	}
}


// Eigener CellRenderer für die spaltenköpfe. Liefert imemr ein JLabel zurück, und implementiert TableCellRenderer
class MyHeaderCellRenderer extends JLabel implements TableCellRenderer {
	
	// einzig wichtige methode
	public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
		// text
		setText(value.toString());
		// normale schriftart
		setFont(table.getFont());
		// der standart rahmen für spaltenköpfe
		setBorder(UIManager.getBorder("TableHeader.cellBorder"));
		// text zentiriert darstellen
		setHorizontalAlignment(SwingConstants.CENTER);
		// tooltip
		setToolTipText("Colum No. "+(column+1));
		// undurchsichtig damit man die hintergrundfarbe sieht.
		setOpaque(true);
		// je nach spalte die hintergrundfarbe setzen
		switch(column) {
			case 0:  setBackground(Color.GREEN); break;
			case 1:  setBackground(Color.BLUE); break;
			case 2:  setBackground(Color.YELLOW); break;
			case 3:  setBackground(Color.RED); break;
			default: setBackground(Color.LIGHT_GRAY);
		}
		return this;
	}
}

Ausgabe:

JTableTest2.jpg


Hier wird zwar nur die Hintergrundfarbe geändert, Aber es gibt noch sehr viel mehr Möglichkeiten Die Spaltenköpfe zu manipulieren mithilfe des CellRenderers. Genau wie in Teil 3 beschrieben kann man auch andere komponenten die Darstellung der Header übernehmen lassen. Dazu muss man allerdings den CellRenderer anpassen (siehe auch Teil:

Code:
class MyHeaderCellRenderer implements TableCellRenderer {
	
	// einzig wichtige methode
	public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
		JComponent c = null;
		if(value instanceof String ) {
			c = new JLabel((String)value);
			((JLabel)c).setHorizontalAlignment(SwingConstants.CENTER);
		} else if(value instanceof JCheckBox) {
			c = (JCheckBox) value;
		} else if(value instanceof JComboBox) {
			c = (JComboBox) value;
		} else {
			c = new JLabel(value.toString());
			((JLabel)c).setHorizontalAlignment(SwingConstants.CENTER);
		}
		c.setEnabled(true);
		// normale schriftart
		c.setFont(table.getFont());
		// der standart rahmen für spaltenköpfe
		c.setBorder(UIManager.getBorder("TableHeader.cellBorder"));
		// text zentiriert darstellen
		c.setToolTipText("Colum No. "+(column+1));
		// undurchsichtig damit man die hintergrundfarbe sieht.
		c.setOpaque(true);
		// je nach spalte die hintergrundfarbe setzen
		switch(column) {
			case 0:  c.setBackground(Color.GREEN); break;
			case 1:  c.setBackground(Color.BLUE); break;
			case 2:  c.setBackground(Color.YELLOW); break;
			case 3:  c.setBackground(Color.RED); break;
			default: c.setBackground(Color.LIGHT_GRAY);
		}
		return c;
	}

Jetzt gibt es aber das Problem dass man die Komponenten nicht ändern kann. Das ist auch etwas komplizierter, aber gute Tutorials,
die sich damit eingehender beschäftigen findet man z.B. hier:
http://www.objects.com.au/java/examples/swing/EditableTableHeader.do
Dort sind auch andere mehrere tiefergehende Beispiele zu JTableHeader und TableColumn.
 
R

Roar

Gast
Im Folgenden noch ein Beispiel zu dem TableEditor und CellRenderer in Einem.
Dieses Beispiel kann man gut für Datei Manager anwenden, es ist auch ein Teil meines FileManager Programms. Es ist ein Editor der es erlaubt bei Doppelklick auf deine Zelle den Dateinamen zu ändern, aber das Icon der Datei sichtbar bleibt. Das ist nur ein kleiner Anriss und selbstverständlich nicht komplett oder fehlerfrei, aber zum Ausbauen gut geeignet.

Code:
//FileTableEditingCell.java - Der Editor Component
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import javax.swing.*;
import javax.swing.table.TableCellEditor;
import javax.swing.filechooser.FileSystemView;

class FileTableEditingCell extends JPanel implements KeyListener {
	
	protected JTextField tf;
	protected JLabel icon;
	protected File f;
	private TableCellEditor editor;
	
	public FileTableEditingCell(File f, TableCellEditor editor) {
		FileSystemView fsv = FileSystemView.getFileSystemView();
		tf = new JTextField(fsv.getSystemDisplayName(f), 20);
		icon = new JLabel(fsv.getSystemIcon(f));
		this.f = f;
		this.editor = editor;
		setLayout(new BorderLayout());
		add(icon, BorderLayout.WEST);
		add(tf, BorderLayout.CENTER);
		tf.addKeyListener(this);
	}
	
	public FileTableEditingCell(File f, boolean showIcon,TableCellEditor editor) {
		FileSystemView fsv = FileSystemView.getFileSystemView();
		tf = new JTextField(fsv.getSystemDisplayName(f), 20);
		icon = new JLabel(fsv.getSystemIcon(f));
		this.f = f;
		this.editor = editor;
		setLayout(new BorderLayout());
		if(showIcon)
			add(icon, BorderLayout.WEST);
		add(tf, BorderLayout.CENTER);
		tf.addKeyListener(this);
	}
	
	public String getText() {
		return tf.getText();
	}
	
	public File getFile() {
		return f;
	}
	
	public void keyPressed(KeyEvent e) {
 		boolean stop = e.getKeyCode() == KeyEvent.VK_ENTER || e.getKeyCode() == KeyEvent.VK_TAB || e.isActionKey();
 		if(e.getKeyCode() == KeyEvent.VK_LEFT || e.getKeyCode() == KeyEvent.VK_RIGHT || e.getKeyCode() == KeyEvent.VK_CAPS_LOCK)
 			stop = false; 
 		if(stop) {
 			((FileTableCellEditor)editor).cancelEditing(tf.getText());
 		}
	}
	
 	public void keyReleased(KeyEvent e) {
 	}
 	
 	public void keyTyped(KeyEvent e) {
 	}
}


// FileTableCellEditor.java - Der Editor


import java.awt.*;
import java.io.File;
import java.util.Date;
import java.text.SimpleDateFormat;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.filechooser.FileSystemView;


public class FileTableCellEditor extends DefaultCellEditor {
	
	protected FileTable table;
	private FileTableEditingCell editorComponent;
	
	public FileTableCellEditor(FileTable table) {
		super(new JTextField());
		this.table = table;
	}

	public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
		String columnName = (String)table.getColumnModel().getColumn(column).getHeaderValue();
		File f = (File)value;
		if(this.table.getRenderer().getRenderMode() == FileTableCellRenderer.DETAILS) {
			if(columnName.equals("Name")) {
				editorComponent = new FileTableEditingCell(f, this);
				return editorComponent;
			}
			return null;		
		} else if(this.table.getRenderer().getRenderMode() == FileTableCellRenderer.THUMBNAIL) {
			editorComponent = new FileTableEditingCell(f, false, this);
			return editorComponent;
		}
		return null;
	}
	
	public void cancelEditing(int row, int col) {
		if(editorComponent != null) {
			File f = editorComponent.getFile();
			File newFile = new File(f.getParent()+ "\\" + editorComponent.getText());
			f.renameTo(newFile);
			newFile.setLastModified(System.currentTimeMillis());
			if(table.getRenderer().getRenderMode() == FileTableCellRenderer.DETAILS) {
				((DefaultTableModel)table.getModel()).insertRow(row, new File[]{newFile, newFile, newFile, newFile});
				((DefaultTableModel)table.getModel()).removeRow(row+1);
			} else if(table.getRenderer().getRenderMode() == FileTableCellRenderer.THUMBNAIL) {
				((DefaultTableModel)table.getModel()).setValueAt(newFile, row, col);
			}
		}
		editorComponent = null;
		cancelCellEditing();
	}
	
	public void cancelEditing(String newName) {
		File f = (File)table.getValueAt(table.getSelectedRow(), table.getSelectedColumn());
		File newFile = new File(f.getParent()+ "\\" + newName);
		f.renameTo(newFile);
		newFile.setLastModified(System.currentTimeMillis());
		if(table.getRenderer().getRenderMode() == FileTableCellRenderer.DETAILS) {
			((DefaultTableModel)table.getModel()).insertRow(table.getSelectedRow(), new File[]{newFile, newFile, newFile, newFile});
			((DefaultTableModel)table.getModel()).removeRow(table.getSelectedRow()+1);		
		} else if(table.getRenderer().getRenderMode() == FileTableCellRenderer.THUMBNAIL) {
			((DefaultTableModel)table.getModel()).setValueAt(newFile, table.getSelectedRow(), table.getSelectedColumn());
		}
		cancelCellEditing();
	}
}
Folgendes ist der CellRenderer für die Table. Es sind teilweise shcon andere Darstellungsmodi eingebaut, die aber noch nicht richtig funktionieren und eigentlich nur zum Testen da sind. Wenn man will kann man sie halt erweitern. Der Editor ist im Moment nur für den DETAILS Modus implementiert.

Code:
// FileTableCellRenderer.java - Der Renderer
import java.awt.*;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.swing.*;
import javax.swing.table.TableCellRenderer;
import javax.swing.filechooser.FileSystemView;


public class FileTableCellRenderer implements TableCellRenderer {
	
	public static final int THUMBNAIL = 0;
	public static final int LIST = 1;
	public static final int DETAILS = 2;
	
	protected int rendermode = DETAILS;
	
	public FileTableCellRenderer() {
		
	}

	public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
		if(value == null)
			return null;
		JLabel label = new JLabel(value.toString());
		String columnName = (String)table.getColumnModel().getColumn(column).getHeaderValue();
		FileSystemView fsv = FileSystemView.getFileSystemView();
		File f;
		if(value instanceof File) {
			f = (File)value;
		} else {
			((FileTableCellEditor)table.getDefaultEditor(Object.class)).cancelEditing(row, column);
			f = (File)table.getValueAt(row, column);
		}
		if(rendermode == DETAILS) {
			table.getTableHeader().setVisible(true);
			if(columnName.equals("Name")) {
				label = new JLabel(fsv.getSystemDisplayName(f), fsv.getSystemIcon(f), SwingConstants.LEFT);
			} else if(columnName.equals("Size")) {
				label = new JLabel(Math.round((float)(f.length()/1024))+" KB", SwingConstants.RIGHT);
			} else if(columnName.equals("Type")) {
				label = new JLabel(fsv.getSystemTypeDescription(f));
			} else if(columnName.equals("Last modified")) {
				label = new JLabel(new SimpleDateFormat("dd.MM.yyyy HH:mm").format(new Date(f.lastModified())));
			}
		} else if(rendermode == THUMBNAIL) {
			table.getTableHeader().setVisible(false);
			if(RexFileSystemView.getRexFileSystemView().isSupportedImage(f)) {
				label = new JLabel(fsv.getSystemDisplayName(f), new ImageIcon(f.getPath()), SwingConstants.CENTER);
				label.setVerticalTextPosition(SwingConstants.BOTTOM);
			} else {
				label = new JLabel(fsv.getSystemDisplayName(f), fsv.getSystemIcon(f), SwingConstants.CENTER);
				label.setVerticalTextPosition(SwingConstants.BOTTOM);
			}
			table.setRowHeight(120);
		}
		label.setOpaque(true);
		label.setBorder(BorderFactory.createEmptyBorder(1,1,1,1));
		label.setFont(table.getFont());
		label.setForeground(table.getForeground());
		label.setBackground(table.getBackground());
		if(hasFocus) {
			label.setBackground((Color)UIManager.getDefaults().get("Table.focusCellBackground"));
			label.setForeground((Color)UIManager.getDefaults().get("Table.focusCellForeground"));
			if(columnName.equals("Name") || rendermode == THUMBNAIL) {
				label.setBorder((javax.swing.border.Border)UIManager.getDefaults().get("Table.focusCellHighlightBorder"));
				label.setBackground(table.getSelectionBackground());
				label.setForeground(table.getSelectionForeground());
			}
		}
		return label;
	}
	
	public void setRenderMode(int i) {
		if(i > 2)
			throw new IllegalArgumentException("Parameter i must be THUMBNAIL, LIST or DETAILS");
		rendermode = i;
	}
	
	public int getRenderMode() {
		return rendermode;
	}
}

Das Alles kann eingebaut werden mit einer Instanz von FileTable.java

Code:
// FileTable.java - Alles in Einem

import java.awt.Dimension;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;

public class FileTable extends JTable {
	
	protected FileTableCellRenderer renderer;
	protected FileTableCellEditor editor;
	
	public FileTable(DefaultTableModel model) {
		super(model);
		setShowGrid(false);
		setIntercellSpacing(new Dimension(5,0));
		renderer = new FileTableCellRenderer();
		editor = new FileTableCellEditor(this);
		setDefaultEditor(Object.class, editor);
		setDefaultRenderer(Object.class, renderer);
		setRowSelectionAllowed(false);
	}
	
	public FileTableCellRenderer getRenderer() {
		return renderer;
	}
	
	public FileTableCellEditor getEditor() {
		return editor;
	}
}

Und so wird das ganze implementiert:

Code:
DefaultTableModel model = new DefaultTableModel(new String[]{"Name","Size","Type","Last modified"}, 0);
FileTable tab = new FileTable(model);
anyContainer.add(new JScrollPane(tab));

Und so kann das am Ende mal aussehen:

FileManager.jpg
 
B

bygones

Gast
Meine Hochachtung [schild=11 fontcolor=000000 shadowcolor=C0C0C0 shieldshadow=1]APPLAUS[/schild]
 
B

Beni

Gast
Roar hat gesagt.:
deathbyaclown hat gesagt.:
Meine Hochachtung [schild=11 fontcolor=000000 shadowcolor=C0C0C0 shieldshadow=1]APPLAUS[/schild]

wer sagt dass wir schon fertig sind? *g*
:D

@Roar oder sonst wer
kennt ihr noch gute Tutorials (abgesehen von Suns-Standart-Tutorial)?

@Roar
Dein letzter Post kommt wohl zu der Kategorie "komplexe Anwendungen" :wink:

Ich denke, folgendes sollte unbedingt noch hinein:
- Ein bisschen Quellcode-Erklärungen und Texte für dazwischen
- Der Hinweis, "immer JScrollPane benutzen".
- Sortieren, sobald man auf den Header klickt (falls niemand was dagegen hat, werd ich da mal was versuchen).

- und ein paar kritische Leser die kleine hilfreiche Anmerkungen machen. Besonders Hinweise an Stellen die unklar sind, wären toll.

Chat hat gesagt.:
KSG9|plak`cs Beni, euer JTable Tutorial ist echt gut, aber ich finde ihr solltet noch n bissl auf die TableModels eingehen..
KSG9|plak`cs und evtl. noch ein bisschen genauer auf die Listener eingehen!

mfg Beni
 
B

Beni

Gast
JTable - 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.
Ein guter Grund ein ausführlicher Beitrag in der FAQ zu verfassen.

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.

1. Das JTable
Sollte diese Component wirklich unbekannt sein, von dem hier sprechen wir:
table_01.png


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

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:
// 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 ));
 
B

Beni

Gast
Teil 7 oder so wäre dann: Fallgruben & Hindernisse.

Was mir bisher eingefallen ist:
  • CellRenderer != CellEditor
  • CellEditor: isCellEditable im TableModel muss true zurückgeben.
 
B

Beni

Gast
JTable - Anwendungen 2 - Das sortierte JTable
Oft sieht man Tabellen, in welchem man die Columns sortieren kann.

Selbstverständlich ist das auch mit dem JTable möglich, es bedeutet allerdings einigen Zusatzaufwand.
Deshalb soll hier auch ein Beispiel aufgeführt sein, wie man das implementieren kann.

Der Grundgedanke dabei ist: zwischen dem TableModel und dem JTable wird ein weiteres TableModel geschaltet, welches das Original-Model sortiert der JTable übergibt (sozusagen ein Übersetzer).

Das System, für Klassen TableCellRenderer / Editoren anzumelden wird übernommen, allerdings für Comparatoren.

Grundsätzlich ist dieses SortedTable so geschrieben, dass auch ganz normale TableModels sortiert werden können.

Ein neuer Listener, der SortetTableListener, kann dem SortedTable hinzugefügt werden, um Veränderungen in der Sortierung zu erfahren.

Standartmässig verwendet der CellRenderer der den Header zeichnet einen solchen Listener um herauszufinden, welche Column wie sortiert ist.

Das ist natürlich nur ein Weg, alles zu lösen, und andere Wege sind denkbar, auch einfachere. Aber dies soll auch nur ein Beispiel sein, und nicht in Programmen verwendet werden.


Das Interface für ein erweitertes TableModel. Es muss nicht verwendet werden, gibt aber dem Programmierer bessere Kontrolle.

Code:
import javax.swing.table.TableModel;

/**
 * Ein erweitertes TableModel
 */
public interface SortableTableModel extends TableModel {
	/**
	 * Gibt an, ob die Rows anhand dieser Column sortierbar sind.
	 * @param column Die Column
	 * @return true, falls die Daten sortierbar sind
	 */
	public boolean isColumnSortable( int column );
}

Ein Interface für Listener, welche über Veränderungen bei der Sortierung informiert werden möchten.
Code:
/**
 * @author Benjamin Sigg
 * @version 1.0
 */
public interface SortedTableListener {
	/**
	 * Wird aufgerufen, falls eine Column neu sortiert wurde.
	 * @param table Das Table, in welchem das Ereigniss stattfand
	 * @param column Die column, im Koordinatensystem des TableModels
	 * @param ascending Die Richtung, true = ascending, false = descending
	 */
	public void sorted( SortedTable table, int column, boolean ascending );
	
	/**
	 * Wird aufgerufen, falls keine Column mehr sortiert ist.
	 * @param table Das Table, in welchem das Ereigniss stattfand
	 */
	public void desorted( SortedTable table );
}

Dieser CellRenderer gibt an, welche Column sortiert ist, indem er einen kleinen Pfeil zeichnet.
Code:
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;

import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JTable;
import javax.swing.border.BevelBorder;
import javax.swing.border.Border;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader;

/**
 * Ein einfacher Header-Renderer für das SortedJTable
 * 
 * @author Benjamin Sigg
 * @version 1.0
 */
public class DefaultSortTableHeaderRenderer extends DefaultTableCellRenderer
		implements SortedTableListener{
	
	private Border normalBorder, selectedBorder;
	private Icon ascendingIcon, descendingIcon;
	
	private int column = -1;
	private boolean ascending = false;
	
	public DefaultSortTableHeaderRenderer(){
		setHorizontalTextPosition( LEFT );
		
		normalBorder = BorderFactory.createBevelBorder( BevelBorder.RAISED );
		selectedBorder = BorderFactory.createBevelBorder( BevelBorder.LOWERED );
		
		ascendingIcon = createAscendingIcon();
		descendingIcon = createDescendingIcon();
	}
	
	public Component getTableCellRendererComponent(JTable table, Object value,
			boolean isSelected, boolean hasFocus, int row, int column) {

        if (table != null) {
            JTableHeader header = table.getTableHeader();
            if (header != null) {
                setForeground(header.getForeground());
                setBackground(header.getBackground());
                setFont(header.getFont());
            }
        }

        setText((value == null) ? "" : value.toString());
		
		if( table.convertColumnIndexToModel( column ) == this.column ){
			setBorder( selectedBorder );
			if( ascending )
				setIcon( ascendingIcon );
			else
				setIcon( descendingIcon );
		}
		else{
			setIcon( null );
			setBorder( normalBorder );
		}
		
		return this;
	}
	
	
	public void desorted( SortedTable table ) {
		column = -1;
	}
	
	public void sorted( SortedTable table, int column, boolean ascending) {
		this.column = column;
		this.ascending = ascending;
	}
	
	protected Icon createAscendingIcon(){
		return new Icon(){
			public int getIconHeight() {
				return 3;
			}
			
			public int getIconWidth() {
				return 5;
			}
			
			public void paintIcon(Component c, Graphics g, int x, int y) {
				g.setColor( Color.BLACK );
				g.drawLine( x, y, x+4, y );
				g.drawLine( x+1, y+1, x+3, y+1 );
				g.drawLine( x+2, y+2, x+2, y+2 );
			}
		};
	}
	protected Icon createDescendingIcon(){
		return new Icon(){
			public int getIconHeight() {
				return 3;
			}
			
			public int getIconWidth() {
				return 5;
			}
			
			public void paintIcon(Component c, Graphics g, int x, int y) {
				g.setColor( Color.BLACK );
				g.drawLine( x, y+2, x+4, y+2 );
				g.drawLine( x+1, y+1, x+3, y+1 );
				g.drawLine( x+2, y, x+2, y );
			}
		};		
	}
}

Das sortierte JTable. Alle wichtigen Zusatzklassen sind als private innere Klasse definiert. Von aussen ist dem JTable nicht viel anzusehen.
Code:
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.text.Collator;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.Vector;

import javax.swing.JTable;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableModel;

/**
 * Ein JTable, das die Einträge nach den Columns sortieren kann.
 * 
 * @author Benjamin Sigg
 * @version 1.0
 */
public class SortedTable extends JTable {
	/** Das TableModel mit den Originaldaten */
	private TableModel model;
	/** Ein TableModel das zwischengeschaltet wird, um die Sortierung vorzunehmen */
	private Model sortModel;
	
	private Listener listener = new Listener();
	private Vector listeners = new Vector();
	
	private Hashtable comparators = new Hashtable();
	private boolean reactionResort = true;
	
	private DefaultSortTableHeaderRenderer renderer = new DefaultSortTableHeaderRenderer();
	
	/**
	 * Standartkonstruktor
	 */
	public SortedTable(){
		sortModel = new Model();
		DefaultSortTableHeaderRenderer renderer = new DefaultSortTableHeaderRenderer();
		addSortedTableListener( renderer );
		
		JTableHeader header = getTableHeader();
		header.setDefaultRenderer( renderer );
		header.addMouseListener( listener );
		
		createDefaultComparators();
		super.setModel( sortModel );
	}
	
	/**
	 * Konstruktor mit Model mit Daten.
	 * @param model Das TableModel, welches Daten enthält.
	 */
	public SortedTable( TableModel model ){
		this();
		setModel( model );
	}
	
	/**
	 * Konstruktor mit Model mit Daten.
	 * @param model Das TableModel, welches Daten enthält.
	 */
	public SortedTable( SortableTableModel model ){
		this();
		setModel( model );
	}
	
	/**
	 * Setzt das TableModel mit den Daten.
	 * @param model Das Model
	 */
	public void setModel( TableModel model ){
		if( model == null )
			throw new IllegalArgumentException( "Model must not be null" );
		
		if( sortModel == null )
			super.setModel( model );
		else{
			TableModel oldModel = this.model;
			this.model = model;
			sortModel.modelChange( oldModel, model );
		}
	}
	
	/**
	 * Gibt das Model zurück, welches die Daten liefert.
	 * @return Das Model
	 */
	public TableModel getOriginalModel(){
		return model;
	}
	
	/**
	 * Gibt den Index der selektierten Rows, im Koordinatensystem
	 * des Original-Models zurück.
	 * @return Der Index
	 */
	public int getOriginalSelectedRow(){
		int row = getSelectedRow();
		if( row != -1 )
			row = sortModel.convertSortToOriginal( row );
		
		return row;
	}
	
	/**
	 * Gibt die Indices der selektierten Rows zurück.
	 * @return Die Indices im Koordinatensystem des Original-Models
	 */
	public int[] getOriginalSelectedRows(){
		int[] rows = getSelectedRows();
		
		for( int i = 0, n = rows.length; i<n; i++ )
			rows[i] = sortModel.convertSortToOriginal( rows[i] );
		
		return rows;
	}
	
	/**
	 * Fügt die angegeben Rows des Original-Models der Selektion hinzu.
	 * @param index0 Der erste Index, inklusive
	 * @param index1 Der zweite Index, inklusive
	 */
	public void addOriginalRowSelectionIntervall( int index0, int index1 ){
		int min = Math.min( index0, index1 );
		int max = Math.max( index0, index1 );
		
		for( int i = min; i <= max; i++ ){
			int index = sortModel.convertSortToOriginal( i );
			addRowSelectionInterval( index, index );
		}
	}
	
	/**
	 * Gibt an, ob Standartmässig neu sortiert werden soll, falls 
	 * das Model Veränderungen meldet. Ist dieser Wert falsch, kann
	 * das die Sortierung bei jeder Veränderung des Models verloren gehen. Die
	 * ursprüngliche Sortierung des Original-Models wird verwendet.
	 * @param resort true, falls neu Sortiert werden soll, andernfalls false
	 */
	public void setResortOnModelChange( boolean resort ){
		reactionResort = resort;
	}
	
	/**
	 * Setzt den Comparator für diese spezielle Klasse.
	 * @param clazz Die Klasse
	 * @param comparator Der zu benutzende Comparator
	 */
	public void setDefaultComparator( Class clazz, Comparator comparator ){
		comparators.put( clazz, comparator );
	}
	
	/**
	 * Gibt den Comparator der angegebenen Column zurück.
	 * @param column Index der Column
	 * @return Der Comparator
	 */
	public Comparator getComparator( int column ){
		return getComparator( model.getColumnClass( column ) );
	}
	
	/**
	 * Gibt den Comparator für eine Klasse zurück.
	 * @param clazz Die Klasse
	 * @return Der Comparator
	 */
	public Comparator getComparator( Class clazz ){
		Comparator comp = internalGetComparator( clazz );
		if( comp == null ){
			Class[] interfaces = clazz.getInterfaces();
			Class comparable = Comparable.class;
			for( int i = 0, n = interfaces.length; i<n; i++ ){
				if( interfaces[i].equals( comparable ))
					return internalGetComparator( comparable );
			}
		}
		
		return comp;
	}
	
	private Comparator internalGetComparator( Class clazz ){
		if( clazz != null ){
			Object value = comparators.get( clazz );
			if( value == null ){
				return internalGetComparator( clazz.getSuperclass() );	
			}
			else
				return (Comparator)value;
		}
		else
			return null;
	}
	
	/**
	 * Gibt an, ob diese Column sortierbar ist. Sollte ein SortableTableModel
	 * verwendet werden, wird dieses Model gefragt, andernfalls wird ein
	 * Comparator zur columnClass gesucht, und falls einer gefunden wird,
	 * wird true zurückgegeben.
	 * @param column Der Index der Column, im TableModel-System
	 * @return true, falls die Column sortierbar ist
	 */
	public boolean isColumnSortable( int column ){
		if( model != null ){
			if ( model instanceof SortableTableModel )
				return ((SortableTableModel)model).isColumnSortable( column );
			else
				return getComparator( model.getColumnClass( column ) ) != null;
		}
		else
			return false;
	}
	
	/**
	 * Setzt den JTableHeader.
	 * @param tableHeader der Header
	 */
	public void setTableHeader(JTableHeader tableHeader) {
		if( tableHeader == null )
			throw new IllegalArgumentException( "Header must not be null" );
		
		JTableHeader oldHeader = getTableHeader();
		if( oldHeader != null ){
			oldHeader.removeMouseListener( listener );
			Object renderer = oldHeader.getDefaultRenderer();
			if( renderer != null && renderer == this.renderer ){
				removeSortedTableListener( (SortedTableListener)renderer );
				renderer = null;
			}
		}
		super.setTableHeader(tableHeader);
		tableHeader.addMouseListener( listener );
	}
	
	/**
	 * Fügt einen SortedTableListener hinzu.
	 * @param listener Der Listener
	 */
	public void addSortedTableListener( SortedTableListener listener ){
		listeners.add( listener );
	}
	
	/**
	 * Entfernt einen SortedTableListener.
	 * @param listener Der Listener
	 */
	public void removeSortedTableListener( SortedTableListener listener ){
		listeners.remove( listener );
	}
	
	/**
	 * Verschickt eine Nachricht an alle Listeners, dass das Table sortiert wurde.
	 * @param column Die Column
	 * @param ascending true, falls aufsteigend sortiert wurde
	 */
	protected void fireSorted( int column, boolean ascending ){
		for( int i = 0, n = listeners.size(); i<n; i++ )
			((SortedTableListener)listeners.get( i )).sorted( this, column, ascending );
		
		sortModel.fireTableDataChanged();
		getTableHeader().repaint();
	}
	
	/**
	 * Verschickt eine Nachricht an alle Listeners, dass das Table nicht
	 * mehr sortiert ist.
	 */
	protected void fireDesorted(){
		for( int i = 0, n = listeners.size(); i<n; i++ )
			((SortedTableListener)listeners.get( i )).desorted( this );
		
		getTableHeader().repaint();
	}
		
	/**
	 * Hebt die Anzeige der Sortierung auf, und es gibt keine keine Reaktion,
	 * falls etwas neues hinzugefügt wird.
	 */
	public void desort(){
		sortModel.desort();
	}
	
	/**
	 * Setzt alle Standartcomperatoren.
	 */
	protected void createDefaultComparators(){
		setDefaultComparator( String.class, Collator.getInstance() );
		setDefaultComparator( Comparable.class, new ComparableComparator());
	}
	
	/**
	 * Stellt das normale JTableHeader her. Dieser Header benutzt ein
	 * {@link DefaultSortTableHeaderRenderer}.
	 */
	protected JTableHeader createDefaultTableHeader() {
		this.renderer = new DefaultSortTableHeaderRenderer();
		final DefaultSortTableHeaderRenderer renderer = this.renderer; 
		
		JTableHeader header = new JTableHeader( super.getColumnModel() ){
			public void setDefaultRenderer(TableCellRenderer defaultRenderer) {
				if( SortedTable.this != null ){
					if( defaultRenderer == renderer )
						addSortedTableListener( renderer );
					else
						removeSortedTableListener( renderer );
				}
				
				super.setDefaultRenderer(defaultRenderer);
			}
		};
		
		return header;
	}
	
	/**
	 * Sortiert den Inhalt des Tables nach der angegebenen Column.
	 * @param column Der Index der zu sortierenden Column.
	 * @param ascending True, falls aufsteigend sortiert werden soll, für
	 * absteigendes Sortieren false
	 */
	public void sort( int column, boolean ascendent ){
		sortModel.sort( column, ascendent );
	}
	
	/**
	 * Dieser MouseListener wird dem JTableHeader hinzugefügt, um die zu
	 * sortierende Column herauszufinden.
	 */
	private class Listener extends MouseAdapter{
		public Listener(){
			
		}
		
		public void mouseClicked(MouseEvent e) {
			JTableHeader header = (JTableHeader)e.getSource();
			int column = convertColumnIndexToModel( header.columnAtPoint( e.getPoint() ));
			
			//System.out.println( column + " " + convertColumnIndexToModel( column )
			//		+ " " + convertColumnIndexToView( column ));
			
			if( column >= 0 && isColumnSortable( column ) )
				sortModel.sort( column );
		}
	}
	
	/**
	 * Dieses Model steht zwischen dem tatsächlichen Model, und der JTable.
	 * Es sorgt für die Sortierung, indem es Rows vertauscht zurückgibt.
	 */
	private class Model extends AbstractTableModel implements TableModelListener{
		private Row[] rows = new Row[0];
		private int rowCount = 0;
		
		private int column = -1;
		private boolean ascendent = false;
		
		/**
		 * Standartkonstruktor
		 */
		public Model(){
		}
		
		/**
		 * Sortiert die angegebene Column.
		 * @param column Der Index der Column
		 */
		public void sort( int column ){
			if( column == this.column ){
				ascendent = !ascendent;
				
				for( int i = 0, j = rowCount-1; i<j; i++, j-- ){
					Row temp = rows[i];
					rows[i] = rows[j];
					rows[j] = temp;
				}
				
				fireTableDataChanged();
				fireSorted( column, ascendent );
			}
			else
				sort( column, true );
		}
		
		/**
		 * Sortiert die aktuelle Column neu.
		 */
		private void resort(){
			sort( column, ascendent );
		}
		
		/**
		 * Sortiert die angegebene Column.
		 * @param column Der Index der Column
		 * @param ascendent true, falls aufsteigend sortiert werden soll, false
		 * sofern absteigend sortiert werden soll.
		 */
		public synchronized void sort( int column, boolean ascendent ){
			this.column = column;
			this.ascendent = ascendent;
			
			if( rowCount > 1 ){
				Arrays.sort( rows, 0, rowCount, new RowComparator() );
				fireTableChanged( new TableModelEvent( this, 0, rowCount-1, 
						TableModelEvent.ALL_COLUMNS, TableModelEvent.UPDATE) );
			}
			fireSorted( column, ascendent );
		}
		
		/**
		 * Hebt die Sortierung auf. Alle Rows bleiben an ihrer aktuellen
		 * Position und werden nicht mehr weiterbewegt.
		 */
		public void desort(){
			fireDesorted();
		}
		
		/**
		 * Wird aufgerufen, falls das TableModel mit den Daten ausgetauscht
		 * wurde.
		 * @param oldModel Das alte Model
		 * @param newModel Das neue Model
		 */
		public synchronized void modelChange( TableModel oldModel, TableModel newModel ){
			if( oldModel != null )
				oldModel.removeTableModelListener( this );
			newModel.addTableModelListener( this );
			
			ensureAllInArray( newModel );
			
			fireTableStructureChanged();
			desort();
		}
		
		/**
		 * Überprüft und versichert, dass alle Rows des Models gespeichert sind.
		 * @param model Das Model, dessen Rows gespeichert sein sollen.
		 */
		private void ensureAllInArray( TableModel model ){
			rowCount = 0;
			int size = model.getRowCount();
			ensureRowArray( size );
			
			for( int i = 0; i < size; i++ ){
				if( rows[i] == null )
					rows[i] = new Row( i );
				else
					rows[i].setIndex( i );
			}
			
			rowCount = size;
		}
		
		/**
		 * Versichert, dass der Row-Array genug lang für <code>capacity</code>
		 * viele Elemente ist.
		 * @param capacity Die minimale Länge des Arrays
		 */
		private void ensureRowArray( int capacity ){
			if( rows.length < capacity ){
				Row[] array = new Row[ Math.max( capacity, rows.length*2 ) + 1 ];
				System.arraycopy( rows, 0, array, 0, rowCount );
				rows = array;
			}
		}
		
		/**
		 * Wandelt den Row-Index des Original-Models in einen Row-Index
		 * dieses Models um.
		 * @param row Der originale Index
		 * @return Der echte Index
		 */
		public int convertSortToOriginal( int row ){
			return rows[ row ].getIndex();
		}
		
		/**
		 * Wandelt den aktuellen Row-Index in den Row-Index des Originals um. 
		 * @param row Der echte Index
		 * @return Der originale Index
		 */
		public int convertOriginalToSort( int row ){
			for( int i = 0; i < rowCount; i++ )
				if( rows[i].getIndex() == row )
					return i;
				
			return -1;
		}
		
		public Class getColumnClass(int columnIndex) {
			return model.getColumnClass( columnIndex );
		}
		public int getColumnCount() {
			if( model == null )
				return 0;
			else
				return model.getColumnCount();
		}
		public String getColumnName(int columnIndex) {
			return model.getColumnName( columnIndex );
		}
		public int getRowCount() {
			if( model == null )
				return 0;
			else
				return model.getRowCount();
		}
		public Object getValueAt(int rowIndex, int columnIndex) {
			return model.getValueAt( rows[ rowIndex ].getIndex(), columnIndex );
		}
		public boolean isCellEditable(int rowIndex, int columnIndex) {
			return model.isCellEditable( rows[ rowIndex ].getIndex(), columnIndex );
		}
		
		public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
			model.setValueAt( aValue, rows[ rowIndex ].getIndex(), columnIndex );
		}
		
		/**
		 * Wird aufgerufen, falls das Original-Model verändert wurde.
		 * @param e Das Event
		 */
		public void tableChanged(TableModelEvent e) {
			int columns = model.getColumnCount();

			if( e.getType() != TableModelEvent.UPDATE ){
				ensureAllInArray( model );
			}
			
			if( e.getFirstRow() == TableModelEvent.HEADER_ROW ){
				fireTableStructureChanged();
				
				if( reactionResort && column >= 0 && column < columns)
					resort();
				else
					desort();
			}
			else if( reactionResort && column >= 0 && column < columns)
				resort();
			else if( column != -1 ){
				desort();
				forward( e );
			}
			else
				forward( e );
		}
		
		/**
		 * Sendet eine Kopie des gegebenen TableModelEvent an alle Listener 
		 * weiter, wobei aber zuerst die Indices der Rows vertauscht werden.
		 * @param e Das Event
		 */
		private void forward( TableModelEvent e ){
			if( e.getFirstRow() == TableModelEvent.HEADER_ROW ){
				fireTableChanged( new TableModelEvent( this, e.getFirstRow(),
						e.getLastRow(), e.getColumn(), e.getType() ) );
			}
			else{
				int first = e.getFirstRow();
				int last = e.getLastRow();
				
				int size = last - first + 1;
				
				int[] realRows = new int[ size ];
				int index = 0;
				int min = rowCount;
				int max = 0;
				
				for( int i = 0; i < rowCount; i++ ){
					if( rows[i].getIndex() >= first && rows[i].getIndex() <= last ){
						realRows[index++] = i;
						min = Math.min( min, i );
						max = Math.max( max, i );
					}
				}
				
				if( max - min + 1 == size )
					fireTableChanged( new TableModelEvent( this, min, max,
							e.getColumn(), e.getType() ) );
				else
					fireTableStructureChanged();
			}
		}
		
		/**
		 * Eine "Wrapper"-Klasse für Rows. Jede Row enthält einen Index
		 * zu einer Original-Row.
		 */
		private class Row {
			private int index;
			
			public Row( int index ){
				this.index = index;
			}
			
			public int getIndex(){
				return index;
			}
			
			public void setIndex( int index ){
				this.index = index;
			}
		}
		
		/**
		 * Vergleicht zwei Rows. Dabei wird wann immer möglich eine Reihenfolge
		 * erstellt.
		 */
		private class RowComparator implements Comparator{
			private int columns;
			private Comparator[] comparators;
			
			public RowComparator(){
				columns = model.getColumnCount();
				comparators = new Comparator[ columns ];
			}

			public int compare(Object o1, Object o2) {
				
				
				Row a = (Row)o1;
				Row b = (Row)o2;
				
				if( comparators[ column ] == null )
					comparators[ column ] = getComparator( column );
					
				int result = compare( comparators[ column ], a, b, column );
				
				if( result == 0 ){
					int index = 0;
					
					while( index < columns && result == 0 ){
						if( index != column ){
							if( isColumnSortable( index )){
								if( comparators[ index ] == null )
									comparators[ index ] = getComparator( index );
									
								result = compare( comparators[ index ], a, b, index );
							}
						}
					}
				}
					
				return result;
			}
			
			private int compare( Comparator comparator, Row a, Row b, int column ){
				if( ascendent )
					return comparator.compare( model.getValueAt( a.getIndex(), column ),
							model.getValueAt( b.getIndex(), column ));
				else
					return comparator.compare( model.getValueAt( b.getIndex(), column ),
							model.getValueAt( a.getIndex(), column ));
			}
		}
	}
	
	/**
	 * Standartcomparator der zwei Comparables vergleicht, indem er
	 * ihre <code>compareTo</code> Methoden benutzt.
	 */
	private class ComparableComparator implements Comparator{
		public int compare(Object o1, Object o2) {
			return ((Comparable)o1).compareTo( o2 );
		}
	}
}

Beispiel zur Verwendung
Jetzt noch ein Beispiel, wie das SortedTable benutzt werden kann.
Dabei wird ein beinahe normales DefaultTableModel verwendet, welches allerdings als Klassenangabe "Double" verwendet.
Diese Tabelle ist editierbar.


Code:
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;

import forum.table.SortedTable;

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 ){
		public Class getColumnClass(int columnIndex) {
			return Double.class;
		}
      };
      
      // Das JTable initialisieren
      JTable table = new SortedTable( model );
      ((SortedTable)table).setResortOnModelChange( false );
      
      // 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( new Double( Math.random() ) );
      
      return vector;
   }
}

Das Resultat
table_12.png


Ich hab dieses Model ziemlich schnell zusammengeschrieben, es ist möglich, dass da noch Bugs vorhanden sind.
Wer einen findet, möge sich doch bei mir melden!
 

McSnoop

Bekanntes Mitglied
Roar hat gesagt.:
Code:
// FileTableCellRenderer.java - Der Renderer

........

			table.getTableHeader().setVisible(false);
			if([b][color=red]RexFileSystemView[/color][/b].getRexFileSystemView().isSupportedImage(f)) {
				label = new JLabel(fsv.getSystemDisplayName(f), new ImageIcon(f.getPath()), SwingConstants.CENTER);

Was hat es mit dem RexFileSystemViem auf sich??? Das bringt er bei mir als cannot be resolved =(

mfg
Snoop
 

TheHighlander

Mitglied
McSnoop hat gesagt.:
Roar hat gesagt.:
Code:
// FileTableCellRenderer.java - Der Renderer

........

			table.getTableHeader().setVisible(false);
			if([b][color=red]RexFileSystemView[/color][/b].getRexFileSystemView().isSupportedImage(f)) {
				label = new JLabel(fsv.getSystemDisplayName(f), new ImageIcon(f.getPath()), SwingConstants.CENTER);

Was hat es mit dem RexFileSystemViem auf sich??? Das bringt er bei mir als cannot be resolved =(

mfg
Snoop

Das würd ich auch gern wissen.
Bei mir zeigt das Ding jedenfalls nix an.
Ok muss zugeben....mach das erste mal Java...hab vorher überhaupt noch nie porgammiert...
Kann mir mal jedamd weiterhlefen....
Ich hab den FileTabel von Beni verwendet.

Wie bekomm ich da denn meine Datein rein???
 
B

Beni

Gast
Was RexFileSystemView ist kann nur Roar erklären.

Aber...
Ok muss zugeben....mach das erste mal Java...hab vorher überhaupt noch nie porgammiert...
... ich würde dir empfehlen mit etwas leichterem einzusteigen, vielleicht mal ein paar Schleifen, ein paar Buttons, ein kleines Spiel...
 
R

Roar

Gast
ach hups... da hab ich ausversehen ne klasse von mir verwendet :-/

irgendwie find ich die auch nichmehr bei mir :roll:

wies aussieht braucht ihr nur die methode isSupportedImage()
die methode sieht einfach implementiert so aus:
Code:
public boolean isSupportedImage(File f) {
String name = f.getName().toLowerCase();
return name.endsWith(".jpg") || name.endsWith(".jpeg") || name.endsWith(".png") || name.endsWith(".gif"); 
}

so nur dahingekritzelt. wenn ihrs richitg machen wollt dann fügt auchnoch unterstützung für großgeschriebenen dateiendungen hinzu.

sry für die verwirrung :-/

edit: stimmt, danke, eagle ;)
 
Status
Nicht offen für weitere Antworten.

Ähnliche Java Themen

Neue Themen


Oben