Galileo Computing < openbook >
Galileo Computing - Programming the Net
Galileo Computing - Programming the Net


Java ist auch eine Insel (2. Aufl.) von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 1.4
Java ist auch eine Insel (2. Auflage)
gp Kapitel 15 Komponenten, Container und Ereignisse
  gp 15.1 Es tut sich was – Ereignisse beim AWT
  gp 15.2 Varianten, das Fenster zu schließen
    gp 15.2.1 Eine Klasse implementiert die Schnittstelle WindowListener
    gp 15.2.2 Adapterklassen nutzen
    gp 15.2.3 Innere Mitgliedsklassen und innere anonyme Klassen
    gp 15.2.4 Generic Listener
  gp 15.3 Komponenten im AWT und in Swing
    gp 15.3.1 Peer-Klassen und Lightweight-Komponenten
    gp 15.3.2 Die Basis aller Komponenten: Component und JComponent
    gp 15.3.3 Proportionales Vergrößern eines Fensters
    gp 15.3.4 Dynamisches Layout während einer Größenänderung
    gp 15.3.5 Hinzufügen von Komponenten
  gp 15.4 Das Swing-Fenster JFrame
    gp 15.4.1 Kinder auf einem Swing-Fenster
    gp 15.4.2 Schließen eines Swing-Fensters
  gp 15.5 Ein Informationstext über die Klasse JLabel
    gp 15.5.1 Mehrzeiliger Text
  gp 15.6 Die Klasse ImageIcon
    gp 15.6.1 Die Schnittstelle Icon
    gp 15.6.2 Was Icon und Image verbindet
  gp 15.7 Eine Schaltfläche (JButton)
    gp 15.7.1 Der aufmerksame ActionListener
    gp 15.7.2 Generic Listener für Schaltflächen-Ereignisse verwenden
    gp 15.7.3 AbstractButton
    gp 15.7.4 JToggleButton
  gp 15.8 Tooltips
  gp 15.9 Horizontale und vertikale Schieberegler
    gp 15.9.1 Der AdjustmentListener, der auf Änderungen hört
  gp 15.10 JSlider
  gp 15.11 Ein Auswahlmenü – Choice, JComboBox
    gp 15.11.1 ItemListener
  gp 15.12 Eines aus vielen – Kontrollfelder (JCheckBox)
    gp 15.12.1 Ereignisse über ItemListener
  gp 15.13 Kontrollfeldgruppen, Optionsfelder und JRadioButton
  gp 15.14 Der Fortschrittsbalken JProgressBar
  gp 15.15 Rahmen (Borders)
  gp 15.16 Symbolleisten alias Toolbars
  gp 15.17 Menüs
    gp 15.17.1 Die Menüleisten und die Einträge
    gp 15.17.2 Menüeinträge definieren
    gp 15.17.3 Mnemonics und Short-Cuts (Accelerator)
    gp 15.17.4 Beispiel für ein Programm mit Menüleisten
  gp 15.18 Popup-Menüs
  gp 15.19 Alles Auslegungssache: Die Layoutmanager
    gp 15.19.1 Null-Layout
    gp 15.19.2 FlowLayout
    gp 15.19.3 BorderLayout
    gp 15.19.4 GridLayout
    gp 15.19.5 Der GridBagLayout-Manager
    gp 15.19.6 Weitere Layoutmanager
  gp 15.20 Der Inhalt einer Zeichenfläche: JPanel
  gp 15.21 Das Konzept des Model-View-Controllers
  gp 15.22 List-Boxen
  gp 15.23 JSpinner
  gp 15.24 Texteingabefelder
    gp 15.24.1 Text in einer Eingabezeile
    gp 15.24.2 Die Oberklasse der JText-Komponenten: JTextComponent
    gp 15.24.3 JPasswordField
    gp 15.24.4 Validierende Eingabefelder
    gp 15.24.5 Mehrzeilige Textfelder
    gp 15.24.6 Die Editor-Klasse JEditorPane
  gp 15.25 Bäume mit JTree-Objekten
    gp 15.25.1 Selektionen bemerken
  gp 15.26 Tabellen mit JTable
    gp 15.26.1 Ein eigenes Modell
    gp 15.26.2 AbstractTableModel
    gp 15.26.3 DefaultTableModel
    gp 15.26.4 Ein eigener Renderer für Tabellen
  gp 15.27 JRootPane und JLayeredPane
  gp 15.28 Dialoge
    gp 15.28.1 Der Farbauswahldialog JColorChooser
    gp 15.28.2 Der Dateiauswahldialog
  gp 15.29 Das Java Look&Feel
  gp 15.30 Die Zwischenablage (Clipboard)
  gp 15.31 Undo durchführen
  gp 15.32 Ereignisverarbeitung auf unterster Ebene
  gp 15.33 AWT, Swing und die Threads
    gp 15.33.1 Warum Swing nicht Thread-sicher ist
    gp 15.33.2 Swing-Elemente bedienen mit invokeLater() und invokeAndWait()
  gp 15.34 Selbst definierte Cursor
    gp 15.34.1 Flackern des Mauszeigers bei Animationen vermeiden
  gp 15.35 Mausrad-Unterstützung
  gp 15.36 Benutzerinteraktionen automatisieren


Galileo Computing

15.26 Tabellen mit JTable  downtop

Mit der Klasse JTable lassen sich auf einfache Weise zweidimensionale Tabellendaten darstellen. Die Java-Bibliothek enthält dafür eine einfache Schnittstelle, die über ein Modell und eine eigene Visualisierung ergänzt werden kann. Die vorgefertigte Implementierung bietet schon vieles an, wie zum Beispiel Änderung der Spaltenbreite, Navigation über Tabulatortasten oder Selektion von Spalten oder Zeilen.

Für JTable gibt es einen Konstruktor, der ein zweidimensionales Feld annimmt und die Tabelle dann darstellt. Für uns fällt dabei wenig Arbeit an. Das 2D-Feld kann sich aus Object[][] oder auch aus Vektoren von Vektoren zusammensetzen. Intern wird ein Objektfeld jedoch in Vektoren kopiert.

Beispiel   Gewünscht ist eine Tabelle mit zwei Spalten aus Strings
String data[][] = { {"A", "B" }, { "U", "V" } };
JTable table = new JTable( data );

Tabelle in einer JScrollPane

Reicht der Platz für die Tabelle im Container nicht aus, so ist es sinnvoll, die Tabelle in eine JScrollPane zu setzen. Auch dann werden erst die Köpfe für die Tabelle angezeigt. Möchten wir die Spaltennamen extra setzen, so geben wir im Konstruktor einen zweiten Parameter mit den Namen an.

Abbildung

Beispiel   Eine Tabelle mit Überschriften in einer JScrollPane

Listing 15.34   SimpleTable.java

package table;

import javax.swing.*; public class SimpleTable { public static void main( String args[] ) { String rowData[][] = { { "Japan", "245" }, { "USA", "240" }, { "Italien", "220" }, { "Spanien", "217" }, {"Türkei", "215"} ,{"England", "214"}, { "Frankreich", "190" }, {"Griechenland", "185" }, { "Deutschland", "180" }, {"Portugal", "170" } }; String columnNames[] = { "Land", "Durchschnittliche Sehdauer pro Tag in Minuten" }; JTable table = new JTable( rowData, columnNames ); JFrame frame = new JFrame(); frame.getContentPane().add( new JScrollPane(table) ); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.pack(); frame.setVisible( true ); } }


Galileo Computing

15.26.1 Ein eigenes Modell  downtop

JTable ist ein gutes Beispiel für die Trennung von Daten und Anzeige. Während View und Controller in der Klasse JTable liegen, werden die Daten im Modell durch die Schnittstelle TableModel beschrieben. Jeder Datencontainer muss daher TableModel implementieren und der Anzeige eine Möglichkeit geben, Einträge in einer Zeile und Spalte zu erfragen. Ändert sich das Modell, muss zusätzlich die Visualisierung aktualisiert werden. Daher schreibt TableModel einen TableModelListener vor, der die Beobachtung übernimmt.

interface javax.swing.table.TableModel

gp  Class getColumnClass( int columnIndex )
Liefert das allgemeinste Klassenobjekt, welches die Spalte beschreiben kann.
gp  int getColumnCount()
Liefert die Anzahl Spalten.
gp  String getColumnName( int columnIndex )
Gibt den Namen der Spalte columnIndex zurück.
gp  int getRowCount()
Liefert die Anzahl der Zeilen.
gp  Object getValueAt( int rowIndex, int columnIndex )
Gibt den Eintrag an der Stelle columnIndex und rowIndex zurück.
gp  void setValueAt( Object aValue, int rowIndex, int columnIndex )
Setzt den Wert an die gegebene Stelle.
gp  boolean isCellEditable( int rowIndex, int columnIndex )
Liefert true, wenn die Zelle an rowIndex und columnIndex editierbar ist.
gp  void addTableModelListener( TableModelListener l )
Fügt einen Ereignisbehandler hinzu, der immer dann informiert wird, wenn Daten geändert werden.
gp  void removeTableModelListener( TableModelListener l )
Entfernt den Ereignisbehandler.

Galileo Computing

15.26.2 AbstractTableModel  downtop

Für TableModel gibt es schon eine Implementierung als abstrakte Klasse, die uns die Aufgabe abnimmt, uns um die Listener zu kümmern. Die Klasse heißt AbstractTableModel und gibt für einige Methoden eine Standard-Implementierung vor. AbstractTableModel bietet Zugriff auf die Listener über eine protected-Variable listenerList.

Um ein lauffähiges Model zusammenzubauen, muss nur noch getColumnCount(), getRowCount() und getValueAt() implementiert werden, dann ist eine Modellklasse komplett. setValueAt() ist in AbstractTableModel leer implementiert und muss nur bei editierbaren Datenmodellen angepasst werden. isCellEditable() liefert false und muss bei editierbaren Modellen ebenso überschrieben werden. getColumnName() liefert Spaltennamen nach dem Muster A, B, C, ... Z, AA, AB. getColumnClass() liefert Object.class. Um nach einer Spalte suchen zu können, gibt findColumn(String) den Index der Spalte zurück, die den eingetragenen Namen hat.

Beispiel   Wenn wir eine Tabelle mit Quadrat und Kubik nutzen, dann können wir ein Modell implementieren, das in der ersten Spalte die Zahl, in der zweiten das Quadrat und in der dritten das Kubik abbildet. Die Tabelle besitzt damit 3 Spalten. Sie soll 10 Zeilen groß sein.

Abbildung

Listing 15.35   QuadratTable.java, QuadratTableModelSimple

package table;

class QuadratTableModelSimple extends AbstractTableModel
{ public int getRowCount()
{ return 10; } public int getColumnCount()
{ return 3; } public Object getValueAt( int row, int col )
{ if ( col == 0 ) return ""+row; else if ( col == 1 ) return ""+(row*row); else return ""+(row*row*row); } }
Abbildung 15.24   JTable mit Model
Abbildung

Ereignisse bei Änderungen

Die Events, die AbstractTableModel auslöst, sind vom Typ TableModelEvent und werden von fireTableDataChanged(), fireTableStructureChanged(), fireTableRowsInserted(), fireTableRowsUpdated(), fireTableRowsDeleted(), fireTableCellUpdated() über die allgemeine Methode fireTableChanged(TableModelEvent) behandelt. Die Methoden zur Ereignisbehandlung sind damit vollständig und müssen von Unterklassen nicht mehr überschrieben werden, es sei denn, wir wollten zusätzliche Dinge in einer fire()-Methode realisieren.

Beispiel   Ändern sich die Daten, muss die Visualisierung erneuert werden. Dann sollte fireTableCellUpdated() aufgerufen werden, wie für die setValueAt()-Methode gezeigt wird.

public void setValueAt( Object val, int row, int column )
{ foo[row][column] = aValue; fireTableCellUpdated( row, column ); }

Die Methode fireTableCellUpdated(int, int) ist nur eine Abkürzung für Folgendes:

public void fireTableCellUpdated(int row, int column) {
fireTableChanged(new TableModelEvent(this, row, row, column)); }

Galileo Computing

15.26.3 DefaultTableModel  downtop

Kommt ein JTable ohne eigenes Modell zum Einsatz, nutzt die Implementierung eine Unterklasse von AbstractTableModel, welche sich DefaultTableModel nennt. Sie ist nicht abstrakt und könnte auch von uns verwendet werden. Nützliche Ergänzungen sind Methoden, damit an beliebiger Stelle Zellen eingetragen, verschoben und gelöscht werden können. Nutzen wir JTable ohne eigenes Modell, so verwendet es standardmäßig DefaultTableModel mit einer Implementierung von Vektoren aus Vektoren. Ein Hauptvektor speichert Vektoren für jede Zeile. Die Technik lässt sich gut an einer Methode ablesen, die ein Wert erfragt:

public Object getValueAt( int row, int column )
{
  Vector rowVector = (Vector)dataVector.elementAt( row );
  return rowVector.elementAt( column );
}

Mit den Methoden setDataVector() und getDataVector() lassen sich die Daten intern setzen und auslesen. Diese interne Abbildung der Daten ist jedoch nicht immer gewünscht, da dynamische Strukturen von der Laufzeit her ineffizient sein können. Ist das zu unflexibel, lässt sich immer noch eine eigens Modell von AbstractTableModel ableiten.

Das Modell nutzen

Besitzen wir eine Klasse, die ein TableModel implementiert, etwa eine Unterklasse von AbstractTableModel oder DefaultTableModel, so können wir ein JTable mit diesem Modell anlegen. Dafür gibt es zwei Möglichkeiten: im Konstruktor das Modell angeben oder es nachträglich mit setModel() zuweisen.

Abbildung

Beispiel   Das QuadratTableModelSimple soll unserer Tabelle zugewiesen werden.
TableModel model = new QuadratTableModelSimple();
JTable table = new JTable(); table.setModel( model );


Galileo Computing

15.26.4 Ein eigener Renderer für Tabellen  toptop

Damit eine Tabelle nicht nur die typischen Informationen in Zeichenketten darstellen muss, lässt sich ein TableCellRenderer einsetzen, damit sich die Tabelleneinträge beliebig visualisieren lassen. Die Schnittstelle TableCellRenderer schreibt nur eine Methode vor.

interface javax.swing.table.TableCellRenderer

gp  Component getTableCellRendererComponent( JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column)
Die Informationen über isSelected, hasFocus, row und column sollen der Zeichenfunktion die Möglichkeit geben, ausgewählte Tabellenteile besonders zu behandeln. Steht etwa auf einer Zelle der Fokus, ist ein Rahmen gezeichnet. Ist die Tabelle selektiert, ist die Zelle mit einer Hintergrundfarbe ausgeschmückt.

DefaultTableCellRenderer

Glücklicherweise bietet Swing wieder eine Standard-Implementierung in Form der Klasse DefaultTableCellRenderer. Diese erweitetert JLabel. Damit lässt sich schon viel anfangen. Das Ändern des Textes ist genauso einfach wie das Ändern der Farbe oder das Hinzufügen eines Bilds. Viele Aufgaben sind so schon erledigt. Wenn es aufwändiger realisiert werden soll, dann müssen wir direkt TableCellRenderer implementieren.

Abbildung

Für unsere Zwecke soll DefaultTableCellRenderer genügen. Die wichtigste Methode zum Überschreiben ist setValue(Object). In DefaultTableCellRenderer sieht die Original-Methode wie folgt aus:

protected void setValue( Object value ) {
setText( (value == null) ? "" : value.toString() ); }

Da JTable diesen Renderer als Standard nutzt, sagt es aus, dass alle Daten in der Tabelle als String-Repräsentation eingesetzt werden.

Wenn wir eigene Visualisierungen wünschen, zum Beispiel mit einer anderen Schriftfarbe, so überschreiben wir einfach setValue() und setzen den Text mit setText() selbst.

Beispiel   In einer Tabelle befinden sich nur Zahlen. Ist eine Zahl negativ, so soll sie rot erscheinen.
public void setValue( Object value )
{ String s = value.toString(); if ( Integer.parseInt( s ) < 0 ) setForeground( Color.red ); setText( s ); }

Die günstige Eigenschaft, dass DefaultTableCellRenderer eine Unterklasse von JLabel ist, macht sich bei setForeground() bemerkbar. Für mehrzeiligen Text machen sich die Unterklassen von JText ganz gut.

Liegen im Modell einer JTable nicht nur Daten einer Gattung, so lassen sie sich mit instanceof aufschlüsseln.

Beispiel   In einer Tabelle sollen Zahlen (etwa vom Typ Integer) und Objekte vom Typ Gfx liegen. Gfx-Objekte enthalten ein Icon-Objekt mit dem Namen icon. Es soll in die Tabelle gesetzt werden.
public void setValue( Object value )
{ if ( value instanceof Gfx ) { Gfx gfx = (IconData)value; setIcon( gfx.icon );
} else super.setValue( value ); } }

Die Behandlung im else-Zweig ist dabei sehr wichtig, denn dort wird der Rest der Daten behandelt. Ist es Text, so kümmert sich die Implementierung von DefaultTableCellRenderer darum. Bei setIcon() profitieren wir wieder von der Erweitung von JLabel.

Beispiel   Unserer Tabelle mit den Quadrat- und Kubikzahlen wollen wir einen Renderer mitgeben. Er soll die geraden Zahlen in Blau anzeigen und die ungeraden in Grau.

Listing 15.36   ColoredTableCellRenderer.java

package table;

import java.awt.*;
import javax.swing.table.*;

class ColoredTableCellRenderer extends DefaultTableCellRenderer
{ public void setValue( Object value ) { if ( value instanceof Long ) { if ( ((Long)value).longValue()%2 == 0 ) setForeground( Color.blue );
else setForeground( Color.gray );

setText( ""+value ); } else super.setValue( value ); } }

Renderer zuweisen

Um diesen zuzuweisen, verbinden wir einen Datentyp einschließlich Renderer mit der Methode setDefaultRenderer() von JTable.

DefaultTableCellRenderer ren = new ColoredTableCellRenderer();
table.setDefaultRenderer( Long.class, ren );

Mehrzeilige Tabellenzellen

Der DefaultTableCellRenderer ist eine Unterklasse von JLabel. Diese unterstützt mehrzeilige Textfelder nicht direkt. Eine Lösung wäre jedoch, die HTML-Darstellung einzuschalten, im Text also etwa <HTML>Zeile1<BR>Zeile2</HTML> zu schreiben. Eine andere Möglichkeit ist es, einen eigenen Renderer zu implementieren, der nicht von DefaultTableCellRenderer abgeleitet ist. Eine gute Lösung ist, JTextArea als Oberklasse zu nutzen und die notwendige Schnittstelle TableCellRenderer zu implementieren. Die implementierte Methode getTableCellRendererComponent() liefert dann das this-Objekt zurück, gesetzt mit dem Text inklusive Zeilenumbruch.

Listing 15.37   DoubleTeamTextCellRenderer.java

package table;

import java.awt.*; import javax.swing.*; import javax.swing.table.*; public class DoubleTeamTextCellRenderer extends JTextArea implements TableCellRenderer { public Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column ) { // Text setzen setText( "1\n2" ); return this; } }




Copyright © Galileo Press GmbH 2003
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de