Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Vorwort
1 Java ist auch eine Sprache
2 Sprachbeschreibung
3 Klassen und Objekte
4 Der Umgang mit Zeichenketten
5 Eigene Klassen schreiben
6 Exceptions
7 Generics<T>
8 Äußere.innere Klassen
9 Besondere Klassen der Java SE
10 Architektur, Design und angewandte Objektorientierung
11 Die Klassenbibliothek
12 Bits und Bytes und Mathematisches
13 Datenstrukturen und Algorithmen
14 Threads und nebenläufige Programmierung
15 Raum und Zeit
16 Dateien, Verzeichnisse und Dateizugriffe
17 Datenströme
18 Die eXtensible Markup Language (XML)
19 Grafische Oberflächen mit Swing
20 Grafikprogrammierung
21 Netzwerkprogrammierung
22 Verteilte Programmierung mit RMI
23 JavaServer Pages und Servlets
24 Datenbankmanagement mit JDBC
25 Reflection und Annotationen
26 Dienstprogramme für die Java-Umgebung
A Die Begleit-DVD
Stichwort
Ihre Meinung?

Spacer
 <<   zurück
Java ist auch eine Insel von Christian Ullenboom
Das umfassende Handbuch
Buch: Java ist auch eine Insel

Java ist auch eine Insel
geb., mit DVD
1482 S., 49,90 Euro
Rheinwerk Computing
ISBN 978-3-8362-1506-0
Pfeil 19 Grafische Oberflächen mit Swing
  Pfeil 19.1 Das Abstract Window Toolkit und Swing
    Pfeil 19.1.1 SwingSet-Demos
    Pfeil 19.1.2 Abstract Window Toolkit (AWT)
    Pfeil 19.1.3 Java Foundation Classes
    Pfeil 19.1.4 Was Swing von AWT unterscheidet
  Pfeil 19.2 Mit NetBeans zur ersten Oberfläche
    Pfeil 19.2.1 Projekt anlegen
    Pfeil 19.2.2 Gui-Klasse hinzufügen
    Pfeil 19.2.3 Programm starten
    Pfeil 19.2.4 Grafische Oberfläche aufbauen
    Pfeil 19.2.5 Swing-Komponenten-Klassen
    Pfeil 19.2.6 Funktionalität geben
  Pfeil 19.3 Fenster unter grafischen Oberflächen
    Pfeil 19.3.1 Swing-Fenster mit javax.swing.JFrame darstellen
    Pfeil 19.3.2 Fenster schließbar machen – setDefaultCloseOperation()
    Pfeil 19.3.3 Sichtbarkeit des Fensters
    Pfeil 19.3.4 Größe und Position des Fensters verändern
    Pfeil 19.3.5 Fenster- und Dialog-Dekoration, Transparenz *
    Pfeil 19.3.6 Dynamisches Layout während einer Größenänderung *
  Pfeil 19.4 Beschriftungen (JLabel)
    Pfeil 19.4.1 Mehrzeiliger Text, HTML in der Darstellung
  Pfeil 19.5 Icon und ImageIcon für Bilder auf Swing-Komponenten
    Pfeil 19.5.1 Die Klasse ImageIcon
    Pfeil 19.5.2 Die Schnittstelle Icon und eigene Icons *
  Pfeil 19.6 Es tut sich was – Ereignisse beim AWT
    Pfeil 19.6.1 Swings Ereignisquellen und Horcher (Listener)
    Pfeil 19.6.2 Listener implementieren
    Pfeil 19.6.3 Listener bei dem Ereignisauslöser anmelden/abmelden
    Pfeil 19.6.4 Aufrufen der Listener im AWT-Event-Thread
    Pfeil 19.6.5 Adapterklassen nutzen
    Pfeil 19.6.6 Innere Mitgliedsklassen und innere anonyme Klassen
    Pfeil 19.6.7 Ereignisse etwas genauer betrachtet *
  Pfeil 19.7 Schaltflächen
    Pfeil 19.7.1 Normale Schaltflächen (JButton)
    Pfeil 19.7.2 Der aufmerksame »ActionListener«
    Pfeil 19.7.3 Schaltflächen-Ereignisse vom Typ »ActionEvent«
    Pfeil 19.7.4 Basisklasse »AbstractButton«
    Pfeil 19.7.5 Wechselknopf (JToggleButton)
  Pfeil 19.8 Swing Action *
  Pfeil 19.9 JComponent und Component als Basis aller Komponenten
    Pfeil 19.9.1 Hinzufügen von Komponenten
    Pfeil 19.9.2 Tooltips (Kurzhinweise)
    Pfeil 19.9.3 Rahmen (Border) *
    Pfeil 19.9.4 Fokus und Navigation *
    Pfeil 19.9.5 Ereignisse jeder Komponente *
    Pfeil 19.9.6 Die Größe und Position einer Komponente *
    Pfeil 19.9.7 Komponenten-Ereignisse *
    Pfeil 19.9.8 Undurchsichtige (opake) Komponente *
    Pfeil 19.9.9 Properties und Listener für Änderungen *
  Pfeil 19.10 Container
    Pfeil 19.10.1 Standardcontainer (JPanel)
    Pfeil 19.10.2 Bereich mit automatischen Rollbalken (JScrollPane)
    Pfeil 19.10.3 Reiter (JTabbedPane)
    Pfeil 19.10.4 Teilungs-Komponente (JSplitPane)
  Pfeil 19.11 Alles Auslegungssache: die Layoutmanager
    Pfeil 19.11.1 Übersicht über Layoutmanager
    Pfeil 19.11.2 Zuweisen eines Layoutmanagers
    Pfeil 19.11.3 Im Fluss mit FlowLayout
    Pfeil 19.11.4 BoxLayout
    Pfeil 19.11.5 Mit BorderLayout in alle Himmelsrichtungen
    Pfeil 19.11.6 Rasteranordnung mit GridLayout
    Pfeil 19.11.7 Der GridBagLayoutmanager *
    Pfeil 19.11.8 Null-Layout *
    Pfeil 19.11.9 Weitere Layoutmanager
  Pfeil 19.12 Rollbalken und Schieberegler
    Pfeil 19.12.1 Schieberegler (JSlider)
    Pfeil 19.12.2 Rollbalken (JScrollBar) *
  Pfeil 19.13 Kontrollfelder, Optionsfelder, Kontrollfeldgruppen
    Pfeil 19.13.1 Kontrollfelder (JCheckBox)
    Pfeil 19.13.2 ItemSelectable, ItemListener und das ItemEvent
    Pfeil 19.13.3 Sich gegenseitig ausschließende Optionen (JRadioButton)
  Pfeil 19.14 Fortschritte bei Operationen überwachen *
    Pfeil 19.14.1 Fortschrittsbalken (JProgressBar)
    Pfeil 19.14.2 Dialog mit Fortschrittsanzeige (ProgressMonitor)
  Pfeil 19.15 Menüs und Symbolleisten
    Pfeil 19.15.1 Die Menüleisten und die Einträge
    Pfeil 19.15.2 Menüeinträge definieren
    Pfeil 19.15.3 Einträge durch Action-Objekte beschreiben
    Pfeil 19.15.4 Mit der Tastatur: Mnemonics und Shortcut
    Pfeil 19.15.5 Der Tastatur-Shortcut (Accelerator)
    Pfeil 19.15.6 Tastenkürzel (Mnemonics)
    Pfeil 19.15.7 Symbolleisten alias Toolbars
    Pfeil 19.15.8 Popup-Menüs
  Pfeil 19.16 Das Model-View-Controller-Konzept
  Pfeil 19.17 Auswahlmenüs, Listen und Spinner
    Pfeil 19.17.1 Auswahlmenü (JComboBox)
    Pfeil 19.17.2 Zuordnung einer Taste mit einem Eintrag *
    Pfeil 19.17.3 Datumsauswahl
    Pfeil 19.17.4 Listen (JList)
    Pfeil 19.17.5 Drehfeld (JSpinner) *
  Pfeil 19.18 Textkomponenten
    Pfeil 19.18.1 Text in einer Eingabezeile
    Pfeil 19.18.2 Die Oberklasse der Text-Komponenten (JTextComponent)
    Pfeil 19.18.3 Geschützte Eingaben (JPasswordField)
    Pfeil 19.18.4 Validierende Eingabefelder (JFormattedTextField)
    Pfeil 19.18.5 Einfache mehrzeilige Textfelder (JTextArea)
    Pfeil 19.18.6 Editor-Klasse (JEditorPane) *
  Pfeil 19.19 Tabellen (JTable)
    Pfeil 19.19.1 Ein eigenes Tabellen-Model
    Pfeil 19.19.2 Basisklasse für eigene Modelle (AbstractTableModel)
    Pfeil 19.19.3 Vorgefertigtes Standard-Modell (DefaultTableModel)
    Pfeil 19.19.4 Ein eigener Renderer für Tabellen
    Pfeil 19.19.5 Zell-Editoren
    Pfeil 19.19.6 Größe und Umrandung der Zellen *
    Pfeil 19.19.7 Spalteninformationen*
    Pfeil 19.19.8 Tabellenkopf von Swing-Tabellen *
    Pfeil 19.19.9 Selektionen einer Tabelle *
    Pfeil 19.19.10 Automatisches Sortieren und Filtern mit RowSorter *
  Pfeil 19.20 Bäume (JTree)
    Pfeil 19.20.1 JTree und sein TreeModel und TreeNode
    Pfeil 19.20.2 Selektionen bemerken
    Pfeil 19.20.3 Das TreeModel von JTree *
  Pfeil 19.21 JRootPane und JDesktopPane *
    Pfeil 19.21.1 Wurzelkomponente der Top-Level-Komponenten (JRootPane)
    Pfeil 19.21.2 JDesktopPane und die Kinder JInternalFrame
    Pfeil 19.21.3 JLayeredPane
  Pfeil 19.22 Dialoge und Window-Objekte
    Pfeil 19.22.1 JWindow und JDialog
    Pfeil 19.22.2 Modal oder nicht-modal
    Pfeil 19.22.3 Standarddialoge mit JOptionPane
    Pfeil 19.22.4 Der Dateiauswahldialog
    Pfeil 19.22.5 Der Farbauswahldialog JColorChooser *
  Pfeil 19.23 Flexibles Java-Look-and-Feel
    Pfeil 19.23.1 Look and Feel global setzen
    Pfeil 19.23.2 UIManager
    Pfeil 19.23.3 Windowsoptik mit JGoodies Looks verbessern *
  Pfeil 19.24 Swing-Komponenten neu erstellen oder verändern *
  Pfeil 19.25 Die Zwischenablage (Clipboard)
    Pfeil 19.25.1 Clipboard-Objekte
    Pfeil 19.25.2 Auf den Inhalt zugreifen mit »Transferable«
    Pfeil 19.25.3 DataFlavor ist das Format der Daten in der Zwischenablage
    Pfeil 19.25.4 Einfügungen in der Zwischenablage erkennen
    Pfeil 19.25.5 Drag
  Pfeil 19.26 AWT, Swing und die Threads
    Pfeil 19.26.1 Ereignisschlange (EventQueue) und AWT-Event-Thread
    Pfeil 19.26.2 Swing ist nicht thread-sicher
    Pfeil 19.26.3 »invokeLater()« und »invokeAndWait()«
    Pfeil 19.26.4 SwingWorker
    Pfeil 19.26.5 Eigene Ereignisse in die Queue setzen *
    Pfeil 19.26.6 Auf alle Ereignisse hören *
  Pfeil 19.27 Barrierefreiheit mit der Java Accessibility API
  Pfeil 19.28 Zeitliches Ausführen mit dem javax.swing.Timer
  Pfeil 19.29 Zum Weiterlesen


Rheinwerk Computing - Zum Seitenanfang

19.19 Tabellen (JTable)  Zur nächsten ÜberschriftZur vorigen Überschrift

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 Model und eine eigene Visualisierung ergänzt werden kann. Die vorgefertigte Implementierung bietet schon vieles an, wie zum Beispiel die Änderung der Spaltenbreite, die Navigation über die Tab -Taste oder die Selektion von Spalten oder Zeilen.

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

Listing 19.44  com/tutego/insel/ui/table/SimpleTable.java

package com.tutego.insel.ui.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 Fernsehdauer pro Tag in Minuten"
    };

    JFrame f = new JFrame();
    f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

    JTable table = new JTable( rowData, columnNames );
    f.add( new JScrollPane( table ) );

    f.pack();
    f.setVisible( true );
  }
}

Wir setzen die Tabelle in eine JScrollPane auch aus dem Grund, dass dann erst die JTable die Köpfe anzeigt. Beim Scrollen in der Tabelle bleiben die Tabellenköpfe immer stehen. Möchten wir die Spaltennamen extra setzen, nimmt der Konstruktor von JTable im zweiten Argument ein Feld mit Spaltennamen an.


Tipp Eine JTable ist nur so hoch, wie sie sein muss, und dehnt sich nicht auf die ihr zur Verfügung stehende Höhe. Das macht sich bemerkbar bei der Farbe, denn der vertikal verbleibene Bereich ist nicht in dem üblichen Tabellenweiß. Soll die Tabelle den ganzen Bereich (Viewport genannt) einnehmen, hilft ein table.setFillsViewportHeight(true).



Rheinwerk Computing - Zum Seitenanfang

19.19.1 Ein eigenes Tabellen-Model  Zur nächsten ÜberschriftZur vorigen Überschrift

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 Model durch die Schnittstelle TableModel beschrieben. Jeder Datencontainer muss TableModel implementieren und der Anzeige eine Möglichkeit geben, Einträge in einer Zeile und Spalte zu erfragen. Ändert sich das Model, muss zusätzlich die Visualisierung aktualisiert werden. Daher schreibt TableModel einen TableModelListener vor, der die Beobachtung übernimmt.


interface javax.swing.table.TableModel

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

Wollen wir auf die inneren Daten zugreifen, benötigen wir das TableModel. Über getModel() lässt sich dies von der JTable erfragen. Wir können die Tabelle auch fragen, welche Zelle selektiert ist.

int col = t.getSelectedColumn();
int row = t.getSelectedRow();
System.out.println( t.getModel().getValueAt(row, col) );

Rheinwerk Computing - Zum Seitenanfang

19.19.2 Basisklasse für eigene Modelle (AbstractTableModel)  Zur nächsten ÜberschriftZur vorigen Überschrift

Für TableModel gibt es schon eine Implementierung als abstrakte Klasse, die uns etwa die Aufgabe abnimmt, Listener an- und abzumelden. In Swing kommt es sehr häufig vor, dass eine Schnittstelle so weit wie möglich von einer Klasse vorimplementiert wird. Dieses Vorgehen ist unter dem Namen Interface/Implementation-Pair bekannt. Die zu TableModel passende Klasse heißt AbstractTableModel und gibt für einige Methoden eine Standardimplementierung vor. AbstractTableModel bietet Zugriff auf die Listener über eine protected-Variable listenerList.

Um ein lauffähiges Model zusammenzubauen, müssen nur noch getColumnCount(), getRowCount() und getValueAt() implementiert werden, dann ist eine Model-Klasse 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.

Ein Quadratzahlen-Model

Wenn wir eine Tabelle mit Quadrat und Kubus nutzen, können wir ein Model implementieren, das in der ersten Spalte die Zahl, in der zweiten das Quadrat und in der dritten den Kubus abbildet. Die Tabelle verfügt dann über drei Spalten. Sie soll hundert Zeilen groß sein.

Listing 19.45  com/tutego/insel/ui/table/QuadratTableModelSimple

package com.tutego.insel.ui.table;

import javax.swing.table.AbstractTableModel;

class SquareTableModelSimple extends AbstractTableModel
{
  @Override public int getRowCount()
  {
    return 100;
  }

  @Override public int getColumnCount()
  {
    return 3;
  }

  @Override public Object getValueAt( int row, int col )
  {
    if ( col == 0 )
      return "" + row;
    else if ( col == 1 )
      return "" + (row * row);
    else
      return "" + (row * row * row);
  }
}

Das Tabellen-Model zuweisen

Verfügen wir über eine Klasse, die ein TableModel implementiert, etwa eine Unterklasse von AbstractTableModel oder DefaultTableModel, so müssen wir ein JTable-Objekt mit diesem Model verbinden. Dafür gibt es zwei Möglichkeiten: Wir können im Konstruktor das Model angeben oder es nachträglich mit setModel(TableModel) zuweisen.

Listing 19.46  com/tutego/insel/ui/table/QuadratTable.java, main()

JFrame f = new JFrame();
f.getContentPane().add( new JScrollPane( new JTable( Umbruch
  new SquareTableModelSimple() ) ) );
f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
f.pack();
f.setVisible( true );

Abbildung 19.21  JTable mit Model

Änderungswünsche

Standardmäßig lassen sich die Zellinhalte nicht ändern. Wenn der Anwender auf eine Zelle klickt, wird es kein Textfeld geben, das eine neue Eingabe ermöglicht. Das ändert sich aber, wenn aus der Schnittstelle TableModel die Methode boolean isCellEditable(int rowIndex, int columnIndex) überschrieben wird und immer dann true liefert, wenn ein Editor eine Änderung der Zelle erlauben soll. Ist diese Änderung für alle Zellen gültig, liefert die Methode immer true; soll zum Beispiel nur die erste Spalte verändert werden dürfen, schreiben wir:

public boolean isCellEditable( int rowIndex, int columnIndex )
{
  return columnIndex == 0;
}

Die Methode isCellEditable() ist aber nur der erste Teil einer Zelländerung. Die JTable (vereinfachen wir es mal) fragt zunächst beim Model über isCellEditable(), ob eine Zelle vom Anwender überhaupt modifiziert werden kann. Wenn das Ergebnis false ist, wird kein Editor angezeigt. Falls das Ergebnis true ist, sucht die JTable einen passenden Editor und ruft nach einer Änderung mit dem neuen Wert die Methode setValueAt(Object aValue, int rowIndex, int columnIndex) auf. Hier muss das Ergebnis in den Datenstrukturen auch wirklich gespeichert werden. Anschließend erfragt die JTable über getValueAt() noch einmal den aktuellen Wert.


Beispiel Über setValueAt() bekommen wir den neuen Wert als erstes Argument. Interessiert uns der alte Wert, können wir ihn aus dem Model erfragen:

void setValueAt( Object aValue, int rowIndex, int columnIndex )
{
  Object oldValue = getValueAt( rowIndex, columnIndex );
}

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 in einer fire()-Methode Zusätzliches realisieren.


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

public void setValueAt( Object val, int row, int column )
{
  foo[row][column] = val;
  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));
}

Rheinwerk Computing - Zum Seitenanfang

19.19.3 Vorgefertigtes Standard-Modell (DefaultTableModel)  Zur nächsten ÜberschriftZur vorigen Überschrift

Praktischerweise bringt die Java-Bibliothek schon eine Model-Klasse mit, die wir direkt verwenden. Dies ist DefaultTableModel, die ebenso eine Unterklasse von AbstractTableModel ist. Nützliche Ergänzungen sind Methoden, damit an beliebiger Stelle Zellen eingetragen, verschoben und gelöscht werden können. Nutzen wir JTable ohne eigenes Model, so verwendet es standardmäßig DefaultTableModel mit einer Implementierung von Vektoren aus Vektoren. Ein Hauptvektor speichert Vektoren für jede Zeile. Die Technik lässt sich gut an einer Methode ablesen:

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

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


Rheinwerk Computing - Zum Seitenanfang

19.19.4 Ein eigener Renderer für Tabellen  Zur nächsten ÜberschriftZur vorigen Überschrift

Damit eine Tabelle nicht nur die typischen Informationen in Zeichenketten darstellen muss, lässt sich ein TableCellRenderer einsetzen, mit dem man die Tabelleneinträge beliebig visualisieren kann. Die Schnittstelle TableCellRenderer schreibt nur eine Operation vor.


interface javax.swing.table.TableCellRenderer

  • Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)

Die Informationen über isSelected, hasFocus, row und column sollen der Zeichenmethode die Möglichkeit geben, ausgewählte Tabellenteile besonders zu behandeln. Steht etwa auf einer Zelle der Fokus, ist ein Rahmen gezeichnet. Ist die Tabelle selektiert, so ist die Zelle mit einer Hintergrundfarbe ausgeschmückt.

DefaultTableCellRenderer

Swing bietet eine Standardimplementierung in Form der Klasse DefaultTableCellRenderer. Diese Klasse erweitert JLabel, und damit lässt sich schon viel anfangen, denn das Ändern des Textes ist genauso einfach wie das Ändern der Farbe oder das Hinzufügen eines Bilds. Viele Aufgaben sind so schon erledigt. Wenn es aufwändiger realisiert werden soll, dann müssen wir direkt TableCellRenderer implementieren.

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

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

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

Wenn wir eigene Visualisierungen wünschen, zum Beispiel mit einer anderen Schriftfarbe, so überschreiben wir einfach setValue() und setzen den Text mit setText() selbst. Die günstige Eigenschaft, dass DefaultTableCellRenderer eine Unterklasse von JLabel ist, macht sich auch bei setForeground() bemerkbar.

Liegen im Model einer JTable nicht nur Daten einer Gattung, so lassen sie sich mit instanceof aufschlüsseln. Unserer Tabelle mit den Quadrat- und Kubuszahlen wollen wir einen Renderer mitgeben. Er soll die geraden Zahlen in Blau anzeigen und die ungeraden in Grau:

Listing 19.47  com/tutego/insel/ui/table/ColoredTableCellRenderer.java

package com.tutego.insel.ui.table;

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

class ColoredTableCellRenderer extends DefaultTableCellRenderer
{
  @Override
  public void setValue( Object value )
  {
    if ( value instanceof Long )
    {
      setForeground( (Long) value % 2 == 0 ? Color.BLUE : Color.GRAY );

      setText( value.toString() );
    }
    else
      super.setValue( value );
  }
}

Die Typanpassung (Long) value veranlasst den Compiler, das long mittels Unboxing aus dem value-Objekt zu extrahieren.


Beispiel In einer Tabelle sollen Zahlen (etwa vom Typ Integer) und Objekte vom Typ Gfx liegen. Gfx-Objekte enthalten ein Icon-Objekt namens icon. Es soll in die Tabelle gesetzt werden:

public void setValue( Object value )
{
  if ( value instanceof Gfx ) {
    Gfx gfx = (IconData) value;
    setIcon( gfx.icon );
  }
  else {
    setIcon( null );
    super.setValue( value );
  }
}

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


Renderer zuweisen

Ein Renderer übernimmt nicht die Darstellung von allen Zellen, sondern nur die von bestimmten Typen. Daher erwartet die Methode setDefaultRenderer() von JTable neben dem Renderer ein Class-Objekt. Nimmt die JTable aus dem Model ein Objekt heraus, erfragt es den Typ und lässt den Zelleninhalt vom Renderer, der mit diesem Typ verbunden ist, zeichnen:

Listing 19.48  com/tutego/insel/ui/table/QuadratTableWithRenderer.java, Ausschnitt

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

Stellt die Tabelle ein Element vom Typ Long.class dar, so überlässt sie die Visualisierung dem zugewiesenen ColoredTableCellRenderer. Der Typ Object.class passt auf alle Zelleninhalte.

Mehrzeilige Tabellenzellen

Der DefaultTableCellRenderer ist eine Unterklasse von JLabel, die mehrzeilige Textfelder durch die HTML-Darstellung unterstützt. Für einen Text müsste etwa <HTML>Zeile1-<BR>Zeile2</HTML> geschrieben werden. Eine andere Möglichkeit besteht darin, einen eigenen Renderer zu implementieren, der nicht von DefaultTableCellRenderer abgeleitet ist. Eine weitere Lösung ist, JTextArea als Oberklasse zu nutzen und die notwendige Schnittstelle TableCellRenderer zu implementieren. Die implementierte Methode getTableCellRendererComponent() liefert dann das this-Objekt (das JLabel) zurück, das mit dem Text inklusive Zeilenumbruch gesetzt ist:

Listing 19.49  com/tutego/insel/ui/table/TwoLinesCellRenderer.java, TwoLinesCellRenderer

public class TwoLinesCellRenderer extends JTextArea implements TableCellRenderer
{
  @Override public Component getTableCellRendererComponent(
    JTable table, Object value,
    boolean isSelected, boolean hasFocus, int row, int column )
  {
    setText( value.toString() );    // Value kann String mit \n enthalten

    return this;
  }
}

Rheinwerk Computing - Zum Seitenanfang

19.19.5 Zell-Editoren  Zur nächsten ÜberschriftZur vorigen Überschrift

Anders als bei der JList kann der Benutzer die Zellen einer JTable editieren. Erlaubt das Tabellen-Model eine Veränderung, so stellt die JTable vordefiniert eine Texteingabezeile dar. Ein eigener Editor implementiert die Schnittstelle javax.swing.table.TableCellEditor mit einer Methode getTableCellEditorComponent(), die die Editor-Komponente liefert. Das kann zum Beispiel ein JTextField sein. Nach der Bearbeitung erfragt die JTable das Ergebnis über die Methode getCellEditorValue(). Auch diese Methode schreibt die Schnittstelle (indirekt) vor:

Listing 19.50  com/tutego/insel/ui/table/SimpleTableCellEditor.java

package com.tutego.insel.ui.table;

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

public class SimpleTableCellEditor
  extends AbstractCellEditor
  implements TableCellEditor
{
  private JTextField component = new JTextField();

  @Override public Component getTableCellEditorComponent(
      JTable table, Object value,
      boolean isSelected, int rowIndex, int colIndex )
  {
    component.setText( value.toString() );

    return component;
  }

  @Override public Object getCellEditorValue()
  {

    return component.getText();
  }
}

Die Schnittstelle TableCellEditor selbst deklariert nur die Methode getTableCellEditorComponent(), doch weil CellEditor die Ober-Schnittstelle ist, ergeben sich insgesamt 1 + 7 zu implementierende Methoden. CellEditor ist eine ganz allgemeine Schnittstelle für beliebige Zellen, etwa auch für die Zellen in einem JTree-Objekt. Die abstrakte Basisklasse AbstractCellEditor implementiert bis auf getCellEditorValue() alle Operationen aus CellEditor. Und da unsere Klasse die Schnittstelle TableCellEditor annehmen muss, bleibt es bei der Implementierung von getCellEditorValue() und getTableCellEditorComponent().


Rheinwerk Computing - Zum Seitenanfang

19.19.6 Größe und Umrandung der Zellen *  Zur nächsten ÜberschriftZur vorigen Überschrift

Jede Zelle hat eine bestimmte Größe, die durch den Zellinhalt vorgegeben ist. Zusätzlich liegt zwischen zwei Zellen immer etwas Freiraum. Dieser lässt sich mit getIntercellSpacing() erfragen und mit setIntercellSpacing() setzen:

table.setIntercellSpacing( new Dimension(gapWidth, gapHeight) );

Soll die Zelle rechts und links zum Beispiel 2 Pixel Freiraum bekommen, ist gapWidth auf 4 zu setzen, denn das Dimension-Objekt gibt immer den gesamten vertikalen und horizontalen Abstand zwischen den Zellen an.

Die Gesamtgröße einer Zelle ist dann die der Margin-Zeile + Zellhöhe beziehungsweise Margin-Spalte + Zellbreite. Da jedoch setIntercellSpacing() die Höhe einer Zeile nicht automatisch anpasst, muss sie ausdrücklich gesetzt werden:

table.setRowHeight( table.getRowHeight() + gapHeight );

Zusätzlich zur Margin erhöht eine Linie den Abstand zwischen den Zellen. Auch dieses Raster (engl. grid) lässt sich modifizieren. Die folgenden Methoden sind auf die JTable angewendet:


setShowGrid( false );

Schaltet die Umrandung aus.

setShowGrid( false ); setShowVerticalLines( true );

Zeigt nur vertikale Linien.

setGridColor( Color.GRAY );

Die Umrandung wird grau.



Rheinwerk Computing - Zum Seitenanfang

19.19.7 Spalteninformationen*  Zur nächsten ÜberschriftZur vorigen Überschrift

Alle Zelleninformationen der Tabelle stecken im Model einer JTable. Informationen über die Spalten stehen allerdings nicht im TableModel, sondern in Objekten vom Typ TableColumn. Jede Spalte bekommt ein eigenes TableColumn-Objekt, und eine Sammlung der Objekte bildet das TableColumnModel, das wie das TableModel ein Datencontainer der JTable ist.


Beispiel Zähle alle TableColumn-Objekte einer JTable table auf:

for ( Enumeration enum = table.getColumnModel().getColumns();
      enum.hasMoreElements(); )
  System.out.println( (TableColumn)eum.nextElement() );

getColumns() bezieht eine Enumeration von TableColumn-Objekten. Soll ein ganz bestimmtes TableColumn-Objekt untersucht werden, kann auch die Methode getColumn(index) genutzt werden.

Liegt ein TableColumn-Objekt vor, lässt sich von diesem die aktuelle minimale und maximale Breite setzen.


Beispiel Ändere die Breite der ersten Spalte auf 100 Pixel:

table.getColumnModel().getColumn( 0 ).setPreferredWidth( 100 );

AUTO_RESIZE

Verändert der Anwender die Breite einer Spalte, ändert er entweder die Gesamtbreite einer Tabelle, oder er ändert automatisch die Breite der anderen Spalten, um die Gesamtbreite nicht zu verändern. Hier gibt es für die JTable unterschiedliche Möglichkeiten, die eine Methode setAutoResizeMode(int mode) bestimmt. Erlaubte Modi sind Konstanten aus JTable und AUTO_RESIZE_OFF, AUTO_RESIZE_NEXT_COLUMN, AUTO_RESIZE_SUBSEQUENT_COLUMNS, AUTO_RESIZE_LAST_COLUMN, AUTO_RESIZE_ALL_COLUMNS. Sinnvoll sind drei von ihnen:

  • AUTO_RESIZE_SUBSEQUENT_COLUMNS. Der Standard. Verändert gleichmäßig die Breiten aller rechts liegenden Spalten.
  • AUTO_RESIZE_NEXT_COLUMN. Ändert nur die Breite der nachfolgenden Spalte.
  • AUTO_RESIZE_OFF. Ändert die Größe der gesamten Tabelle. Ist nur sinnvoll, wenn die JTable in einer JScrollPane liegt.

Rheinwerk Computing - Zum Seitenanfang

19.19.8 Tabellenkopf von Swing-Tabellen *  Zur nächsten ÜberschriftZur vorigen Überschrift

Der Kopf (engl. header) einer JTable ist ein JTableHeader-Objekt, das von der JTable mit getTableHeader() erfragt werden kann. Dieses JTableHeader-Objekt ist für die Anordnung und Verschiebung der Spalten verantwortlich. Diese Verschiebung kann über das Programm erfolgen (moveColumn()) oder über den Benutzer per Drag & Drop.


Beispiel In der JTable table sollen die Spalten nicht mehr vom Benutzer verschoben werden können. Er soll auch die Breite nicht mehr ändern dürfen:

table.getTableHeader().setReorderingAllowed( false );
table.getTableHeader().setResizingAllowed( false );

Hier wird deutlich, dass ein JTableHeader die Steuerung der Ausgabe und der Benutzerinteraktion übernimmt, aber die Informationen selbst in TableColumn liegen.


Rheinwerk Computing - Zum Seitenanfang

19.19.9 Selektionen einer Tabelle *  Zur nächsten ÜberschriftZur vorigen Überschrift

In einer JTable können auf unterschiedliche Art und Weise Zellen selektiert werden: zum einen nur in einer Zeile oder Spalte, zum anderen kann auch ein ganzer Block oder können auch beliebige Zellen selektiert werden. Die Art der Selektion bestimmen Konstanten in ListSelectionModel. So wird SINGLE_SELECTION nur die Selektion einer einzigen Zelle zulassen.


Beispiel In einer JTable soll entweder ein ununterbrochener Block Zeilen oder Spalten ausgewählt werden dürfen:

table.setSelectionMode( ListSelectionModel.SINGLE_INTERVAL_SELECTION );

Mit Methoden lassen sich im Programm alle Elemente einer Spalte oder Zeile selektieren. Die Selektier-Erlaubnis geben zunächst zwei Methoden:

table.setColumnSelectionAllowed( boolean );
table.setRowSelectionAllowed( boolean );

Die Selektion von Spalten gelingt mit setColumnSelectionInterval(), weitere Bereiche lassen sich mit addColumnSelectionInterval() hinzufügen und mit removeColumnSelectionInterval() entfernen. Das Gleiche gilt für die Methoden, die »Row« im Methodennamen tragen.

Schauen wir uns einige Beispiele an: Selektiere in einer JTable table die Spalte 0 komplett:

table.setSelectionMode( ListSelectionModel.MULTIPLE_INTERVAL_SELECTION );
table.setColumnSelectionAllowed( true );
table.setRowSelectionAllowed( false );
table.setColumnSelectionInterval( 0, 0 );

Selektiere in einer Tabelle nur die Zelle 38, 5:

table.setSelectionMode( ListSelectionModel.SINGLE_SELECTION  );
table.setColumnSelectionAllowed( true );
table.setRowSelectionAllowed( true );
table.changeSelection( 38, 5, false, false );

Als Selektionsmodus reicht SINGLE_SELECTION aus, MULTIPLE_INTERVAL_SELECTION wäre aber auch in Ordnung. Beide Selektionen sind zusammen in der Form nicht möglich. Bei einer Einzelselektion wird die Zelle nur umrandet, aber nicht wie beim Standard-Metal-Look-and-Feel blau ausgefüllt.

Die Methode selectAll() selektiert alle Elemente, clearSelection() löscht alle Selektionen.


Rheinwerk Computing - Zum Seitenanfang

19.19.10 Automatisches Sortieren und Filtern mit RowSorter *  topZur vorigen Überschrift

Nicht immer sollen alle Daten aus dem Modell so in der Tabelle angezeigt werden. Mitunter ist nur ein Ausschnitt interessant, oder für die Anzeige sollen Werte sortiert werden. All das kann über eine Änderung des Models gemacht werden, aber das ist nicht flexibel. Stattdessen sieht die Java-Bibliothek einen RowSorter vor, auf den die JTable zurückgreift, um auch ohne Änderungen am eigenen Modell Elemente herauszufiltern oder Spalten zu sortieren.

Wir wollen eine einfache JTable mit folgendem Model verwenden, die der RowSorter anschließend dekorieren soll:

Listing 19.51  TableWithRowSorter.java, main() Ausschnitt

TableModel model = new DefaultTableModel( 100, 3 ) {
  @Override public Object getValueAt( int row, int column ) {
    return "" + (int) Math.pow( row, column + 1 );
  }
};
final JTable table = new JTable( model );

An dem Beispiel ist abzulesen, dass das ursprüngliche model direkt an JTable geht. Der TableRowSorter ist eine Implementierung vom RowSorter und bekommt das originale TableModel im Konsturktor übergeben:

Listing 19.52  TableWithRowSorter.java, main() Ausschnitt

final TableRowSorter<TableModel> rowSorter = new TableRowSorter<TableModel>( model );
table.setRowSorter( rowSorter );

Starten wir das Programm, ist schon eine Sortierung eingebaut, allerdings nur auf String-Ebene, sodass etwa »19« < »2« < »20« ist. Mit einem Klick auf die Kopfzeile der Tabelle zeigt ein kleiner Pfeil die Sortierrichtung an.

Comparator für die Sortierung zuweisen

Da der RowSorter standardmäßig die Inhalte als String sortiert, wollen wir im nächsten Beispiel für die erste Spalte einen Comparator deklarieren, der nach der Anzahl der gesetzten Bits geht:

Listing 19.53  TableWithRowSorter.java, main() Ausschnitt

rowSorter.setComparator( 0, new Comparator<String>() {
  @Override public int compare( String s1, String s2 )
  {
    int i1 = Integer.parseInt( s1 ), i2 = Integer.parseInt( s2 );
    return Integer.bitCount( i1 ) – Integer.bitCount( i2 );
  }
} );

Die zweite Spalte soll nicht sortierbar sein, sodass wir die Sortierung abschalten:

Listing 19.54  TableWithRowSorter.java, main() Ausschnitt

rowSorter.setSortable( 1, false );

Die dritte Spalte bleibt weiterhin mit der String-Sortierung.

Filter

Auf die Ergebnismenge lassen sich Filter anwenden, die Elemente herausnehmen. Es gibt einige vordefinierte Filter, die die statischen Methoden dateFilter(), numberFilter() und regexFilter() von RowFilter liefern. Mit setRowFilter() wird er zugewiesen:

Listing 19.55  TableWithRowSorter.java, main() Ausschnitt

rowSorter.setRowFilter( RowFilter.regexFilter("(0|2|4|6|8)$", 2) );

Der RowFilter lässt alle geraden Zahlen in der dritten Spalte durch – das filtert alle Zeilen heraus, in denen der Wert der dritten Spalte ungerade ist.

Stellungswechsel

Wird eine Zelle in der Tabelle angeklickt, und ein Listener meldet den Klick, müssen wir natürlich das richtige Model nach den Daten fragen. Es gibt einmal das originale Modell und einmal das Modell vom TableRowSorter. Wir müssen daran denken, dass TableRowSorter das originale Modell dekoriert, und daher sind Anfragen am originalen Tabellenmodell nicht zielführend, wenn Zeilen ausgefiltert wurden. Ein Listener zeigt das:

Listing 19.56  TableWithRowSorter.java, main() Ausschnitt

table.addMouseListener( new MouseAdapter()
{
  @Override
  public void mouseClicked( MouseEvent e )
  {
    int rowAtPoint    = table.rowAtPoint( e.getPoint() );
    int columnAtPoint = table.columnAtPoint( e.getPoint() );
    System.out.printf( "%d/%d%n", rowAtPoint, columnAtPoint );

    int convertedRowAtPoint = rowSorter.convertRowIndexToModel( rowAtPoint );
    int convertedColumAtPoint = table.convertColumnIndexToModel( columnAtPoint );
    System.out.println( rowSorter.getModel().getValueAt( convertedRowAtPoint,
                                                         convertedColumAtPoint) );
  }
} );

Nehmen wir einen Klick auf die Postionen 2/0 an, was für die dritte Zeile und erste Spalte steht. Es ist dann rowAtPoint = 2 und columnAtPoint = 0. Fragen wir bei dem originalen Modell nach, welches die Zahlen 0, 1, 2, 3, … repräsentiert, bekommen wir bei getValueAt(2,0) eine 2. In der Darstellung steht aber keine 2 in der dritten Zeile, sondern eine 4. Und die 4 kommt aus dem internen Modell vom RowSorter. Also muss gemappt werden, und das ist Aufgabe von convertRowIndexToModel(2), das 4 liefert.

Wenn die Spalte zum Beispiel per Drag & Drop verschoben wurde, muss auch columnAtPoint gemappt werden. Das übernimmt die Tabelle selbst, und sie deklariert eine Methode convertColumnIndexToModel(). Da der RowSorter – wie der Name schon sagt – nur auf Zeilen arbeitet, ist bei ihm die Methode nicht zu finden.

Altenative Implementierungen

Eine Alternative beziehungsweise Erweiterung zu den in Java 6 eingefügten Klassen bieten die zwei folgenden Lösungen:

  • JXTable (http://swinglabs.org/) ist eine quelloffene und frei verfügbare Erweiterung einer JTable, die Sortierung, Hervorhebung und Filterung einfach unterstützt. Zu SwingX kommen wir später noch.
  • Renderpack Render Pipelines (https://renderpack.dev.java.net/) hat keine Abhängigkeit zur JTable (oder JList), sondern definiert allgemeine Swing-Renderer.

Sie funktionieren auch vor Java 6, also unter Java 5 und älter.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen. >> Zum Feedback-Formular
 <<   zurück
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Java ist auch eine Insel






 Java ist auch
 eine Insel


Zum Katalog: Java SE Bibliotheken






 Java SE Bibliotheken


Zum Katalog: Professionell entwickeln mit Java EE 7






 Professionell
 entwickeln mit
 Java EE 7


Zum Katalog: Einstieg in Eclipse






 Einstieg in
 Eclipse


Zum Katalog: Einstieg in Java






 Einstieg in
 Java


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo




Copyright © Rheinwerk Verlag GmbH 2011
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.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de