Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.
 
Inhaltsverzeichnis
Vorwort
1 Neues in Java 8 und Java 7
2 Fortgeschrittene String-Verarbeitung
3 Threads und nebenläufige Programmierung
4 Datenstrukturen und Algorithmen
5 Raum und Zeit
6 Dateien, Verzeichnisse und Dateizugriffe
7 Datenströme
8 Die eXtensible Markup Language (XML)
9 Dateiformate
10 Grafische Oberflächen mit Swing
11 Grafikprogrammierung
12 JavaFX
13 Netzwerkprogrammierung
14 Verteilte Programmierung mit RMI
15 RESTful und SOAP-Web-Services
16 Technologien für die Infrastruktur
17 Typen, Reflection und Annotationen
18 Dynamische Übersetzung und Skriptsprachen
19 Logging und Monitoring
20 Sicherheitskonzepte
21 Datenbankmanagement mit JDBC
22 Java Native Interface (JNI)
23 Dienstprogramme für die Java-Umgebung
Stichwortverzeichnis

Jetzt Buch bestellen
Ihre Meinung?

Spacer
<< zurück
Java SE 8 Standard-Bibliothek von Christian Ullenboom
Das Handbuch für Java-Entwickler
Buch: Java SE 8 Standard-Bibliothek

Java SE 8 Standard-Bibliothek
Pfeil 10 Grafische Oberflächen mit Swing
Pfeil 10.1 AWT, JavaFoundation Classes und Swing
Pfeil 10.1.1 Das Abstract Window Toolkit (AWT)
Pfeil 10.1.2 Java Foundation Classes (JFC)
Pfeil 10.1.3 Was Swing von AWT-Komponenten unterscheidet
Pfeil 10.2 Mit NetBeans zur ersten Swing-Oberfläche
Pfeil 10.2.1 Projekt anlegen
Pfeil 10.2.2 Eine GUI-Klasse hinzufügen
Pfeil 10.2.3 Programm starten
Pfeil 10.2.4 Grafische Oberfläche aufbauen
Pfeil 10.2.5 Swing-Komponenten-Klassen
Pfeil 10.2.6 Funktionalität geben
Pfeil 10.3 Aller Swing-Anfang – Fenster zur Welt
Pfeil 10.3.1 Eine Uhr, bei der die Zeit nie vergeht
Pfeil 10.3.2 Swing-Fenster mit javax.swing.JFrame darstellen
Pfeil 10.3.3 Mit add(…) auf den Container
Pfeil 10.3.4 Fenster schließbar machen – setDefaultCloseOperation(int)
Pfeil 10.3.5 Sichtbarkeit des Fensters
Pfeil 10.3.6 Größe und Position des Fensters verändern
Pfeil 10.3.7 Fenster- und Dialogdekoration, Transparenz *
Pfeil 10.3.8 Die Klasse Toolkit *
Pfeil 10.3.9 Zum Vergleich: AWT-Fenster darstellen *
Pfeil 10.4 Beschriftungen (JLabel)
Pfeil 10.4.1 Mehrzeiliger Text, HTML in der Darstellung
Pfeil 10.5 Icon und ImageIcon für Bilder auf Swing-Komponenten
Pfeil 10.5.1 Die Klasse ImageIcon
Pfeil 10.6 Es tut sich was – Ereignisse beim AWT
Pfeil 10.6.1 Die Ereignisquellen und Horcher (Listener) von Swing
Pfeil 10.6.2 Listener implementieren
Pfeil 10.6.3 Listener bei dem Ereignisauslöser anmelden/abmelden
Pfeil 10.6.4 Adapterklassen nutzen
Pfeil 10.6.5 Innere Mitgliedsklassen und innere anonyme Klassen
Pfeil 10.6.6 Aufrufen der Listener im AWT-Event-Thread
Pfeil 10.6.7 Ereignisse, etwas genauer betrachtet *
Pfeil 10.7 Schaltflächen
Pfeil 10.7.1 Normale Schaltflächen (JButton)
Pfeil 10.7.2 Der aufmerksame ActionListener
Pfeil 10.7.3 Schaltflächen-Ereignisse vom Typ ActionEvent
Pfeil 10.7.4 Basisklasse AbstractButton
Pfeil 10.7.5 Wechselknopf (JToggleButton)
Pfeil 10.8 Textkomponenten
Pfeil 10.8.1 Text in einer Eingabezeile
Pfeil 10.8.2 Die Oberklasse der Textkomponenten (JTextComponent)
Pfeil 10.8.3 Geschützte Eingaben (JPasswordField)
Pfeil 10.8.4 Validierende Eingabefelder (JFormattedTextField)
Pfeil 10.8.5 Einfache mehrzeilige Textfelder (JTextArea)
Pfeil 10.8.6 Editor-Klasse (JEditorPane) *
Pfeil 10.9 Swing Action *
Pfeil 10.10 JComponent und Component als Basis aller Komponenten
Pfeil 10.10.1 Hinzufügen von Komponenten
Pfeil 10.10.2 Tooltips (Kurzhinweise)
Pfeil 10.10.3 Rahmen (Border) *
Pfeil 10.10.4 Fokus und Navigation *
Pfeil 10.10.5 Ereignisse jeder Komponente *
Pfeil 10.10.6 Die Größe und Position einer Komponente *
Pfeil 10.10.7 Komponenten-Ereignisse *
Pfeil 10.10.8 UI-Delegate – der wahre Zeichner *
Pfeil 10.10.9 Undurchsichtige (opake) Komponente *
Pfeil 10.10.10 Properties und Listener für Änderungen *
Pfeil 10.11 Container
Pfeil 10.11.1 Standardcontainer (JPanel)
Pfeil 10.11.2 Bereich mit automatischen Rollbalken (JScrollPane)
Pfeil 10.11.3 Reiter (JTabbedPane)
Pfeil 10.11.4 Teilungskomponente (JSplitPane)
Pfeil 10.12 Alles Auslegungssache – die Layoutmanager
Pfeil 10.12.1 Übersicht über Layoutmanager
Pfeil 10.12.2 Zuweisen eines Layoutmanagers
Pfeil 10.12.3 Im Fluss mit FlowLayout
Pfeil 10.12.4 BoxLayout
Pfeil 10.12.5 Mit BorderLayout in alle Himmelsrichtungen
Pfeil 10.12.6 Rasteranordnung mit GridLayout
Pfeil 10.12.7 Der GridBagLayoutmanager *
Pfeil 10.12.8 Null-Layout *
Pfeil 10.12.9 Weitere Layoutmanager
Pfeil 10.13 Rollbalken und Schieberegler
Pfeil 10.13.1 Schieberegler (JSlider)
Pfeil 10.13.2 Rollbalken (JScrollBar) *
Pfeil 10.14 Kontrollfelder, Optionsfelder, Kontrollfeldgruppen
Pfeil 10.14.1 Kontrollfelder (JCheckBox)
Pfeil 10.14.2 ItemSelectable, ItemListener und das ItemEvent
Pfeil 10.14.3 Sich gegenseitig ausschließende Optionen (JRadioButton)
Pfeil 10.15 Fortschritte bei Operationen überwachen *
Pfeil 10.15.1 Fortschrittsbalken (JProgressBar)
Pfeil 10.15.2 Dialog mit Fortschrittsanzeige (ProgressMonitor)
Pfeil 10.16 Menüs und Symbolleisten
Pfeil 10.16.1 Die Menüleisten und die Einträge
Pfeil 10.16.2 Menüeinträge definieren
Pfeil 10.16.3 Einträge durch Action-Objekte beschreiben
Pfeil 10.16.4 Mit der Tastatur – Mnemonics und Shortcut
Pfeil 10.16.5 Der Tastatur-Shortcut (Accelerator)
Pfeil 10.16.6 Tastenkürzel (Mnemonics)
Pfeil 10.16.7 Symbolleisten alias Toolbars
Pfeil 10.16.8 Popup-Menüs
Pfeil 10.16.9 System-Tray nutzen *
Pfeil 10.17 Das Model-View-Controller-Konzept
Pfeil 10.18 Auswahlmenüs, Listen und Spinner
Pfeil 10.18.1 Listen (JList)
Pfeil 10.18.2 Auswahlmenü (JComboBox)
Pfeil 10.18.3 Drehfeld (JSpinner) *
Pfeil 10.18.4 Datumsauswahl
Pfeil 10.19 Tabellen (JTable)
Pfeil 10.19.1 Ein eigenes Tabellen-Model
Pfeil 10.19.2 Basisklasse für eigene Modelle (AbstractTableModel)
Pfeil 10.19.3 Ein vorgefertigtes Standardmodell (DefaultTableModel)
Pfeil 10.19.4 Ein eigener Renderer für Tabellen
Pfeil 10.19.5 Zell-Editoren
Pfeil 10.19.6 Automatisches Sortieren und Filtern mit RowSorter *
Pfeil 10.20 Bäume (JTree)
Pfeil 10.20.1 JTree und sein TreeModel und TreeNode
Pfeil 10.20.2 Selektionen bemerken
Pfeil 10.20.3 Das TreeModel von JTree *
Pfeil 10.21 JRootPane und JDesktopPane *
Pfeil 10.21.1 Wurzelkomponente der Top-Level-Komponenten (JRootPane)
Pfeil 10.21.2 JDesktopPane und die Kinder von JInternalFrame
Pfeil 10.21.3 JLayeredPane
Pfeil 10.22 Dialoge und Window-Objekte
Pfeil 10.22.1 JWindow und JDialog
Pfeil 10.22.2 Modal oder nichtmodal?
Pfeil 10.22.3 Standarddialoge mit JOptionPane
Pfeil 10.22.4 Der Dateiauswahldialog
Pfeil 10.22.5 Der Farbauswahldialog JColorChooser *
Pfeil 10.23 Flexibles Java-Look-and-Feel
Pfeil 10.23.1 Look-and-Feel global setzen
Pfeil 10.23.2 UIManager
Pfeil 10.23.3 Die Windows-Optik mit JGoodies Looks verbessern *
Pfeil 10.24 Swing-Komponenten neu erstellen oder verändern *
Pfeil 10.24.1 Überlagerungen mit dem Swing-Komponenten-Dekorator JLayer
Pfeil 10.25 Die Zwischenablage (Clipboard)
Pfeil 10.25.1 Clipboard-Objekte
Pfeil 10.25.2 Mit Transferable auf den Inhalt zugreifen
Pfeil 10.25.3 DataFlavor ist das Format der Daten in der Zwischenablage
Pfeil 10.25.4 Einfügungen in der Zwischenablage erkennen
Pfeil 10.25.5 Drag & Drop
Pfeil 10.26 Undo durchführen *
Pfeil 10.27 AWT, Swing und die Threads
Pfeil 10.27.1 Ereignisschlange (EventQueue) und AWT-Event-Thread
Pfeil 10.27.2 Swing ist nicht threadsicher
Pfeil 10.27.3 invokeLater(…) und invokeAndWait(…)
Pfeil 10.27.4 SwingWorker
Pfeil 10.27.5 Eigene Ereignisse in die Queue setzen *
Pfeil 10.27.6 Auf alle Ereignisse hören *
Pfeil 10.28 Barrierefreiheit mit der Java Accessibility API
Pfeil 10.29 Zeitliches Ausführen mit dem javax.swing.Timer
Pfeil 10.30 Die Zusatzkomponentenbibliothek SwingX
Pfeil 10.30.1 Im Angebot: Erweiterte und neue Swing-Komponenten
Pfeil 10.30.2 Überblick über erweiterte Standard-Swing-Klassen
Pfeil 10.30.3 Neue Swing-Klassen
Pfeil 10.30.4 Weitere SwingX-Klassen
Pfeil 10.30.5 SwingX-Installation
Pfeil 10.31 Zum Weiterlesen
 
Zum Seitenanfang

10.19Tabellen (JTable) Zur vorigen ÜberschriftZur nächsten Ü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 (ÿ_)-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 2D-Feld kann sich aus Object[][] oder auch aus Vektoren von Vektoren zusammensetzen. Intern wird ein Objektfeld jedoch in Vektoren kopiert.

Listing 10.59com/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.

Einfache Tabelle

Abbildung 10.59Einfache Tabelle

[+]Tipp

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

 
Zum Seitenanfang

10.19.1Ein eigenes Tabellen-Model Zur vorigen ÜberschriftZur nächsten Ü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.

Schnittstelle TableModel

Abbildung 10.60Schnittstelle TableModel

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) );
 
Zum Seitenanfang

10.19.2Basisklasse für eigene Modelle (AbstractTableModel) Zur vorigen ÜberschriftZur nächsten Ü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 über eine protected-Variable listenerList Zugriff auf die Listener.

Um ein lauffähiges Model zusammenzubauen, müssen nur noch getColumnCount(), getRowCount() und getValueAt(int rowIndex, int columnIndex) implementiert werden, dann ist eine Model-Klasse komplett. setValueAt(Object aValue, int rowIndex, int columnIndex) ist in AbstractTableModel leer implementiert und muss nur bei editierbaren Datenmodellen angepasst werden. isCellEditable(int rowIndex, int columnIndex) liefert false und muss bei editierbaren Modellen ebenso überschrieben werden. getColumnName(int columnIndex) liefert Spaltennamen nach dem Muster A, B, C, …, Z, AA, AB. getColumnClass(int columnIndex) liefert Object.class. Um nach einer Spalte suchen zu können, gibt findColumn(String columnName) den Index der Spalte zurück, die den eingetragenen Namen hat – diese Methode gibt es nur in AbstractTableModel, aber die Schnittstelle TableModel schreibt sie nicht vor.

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 10.60com/tutego/insel/ui/table/SquareTableModelSimple.java

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 String getValueAt( int row, int col ) {
if ( col == 0 )
return "" + row;
else if ( col == 1 )
return "" + (row * row);
else
return "" + (row * row * row);
}
}

Statt dass getValueAt(int, int) nur Object zurückgibt, nutzt das Beispiel kovariante Rückgabetypen und liefert den konkreteren Typ String zurück.

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 10.61com/tutego/insel/ui/table/SquareTable.java, main()

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

Abbildung 10.61JTable 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 in der Modellklasse:

@Override 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.

[zB]Beispiel

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

@Override 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.

[zB]Beispiel

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

@Override public void setValueAt( Object val, int row, int column ) {
// zum Beispiel 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));
}
 
Zum Seitenanfang

10.19.3Ein vorgefertigtes Standardmodell (DefaultTableModel) Zur vorigen ÜberschriftZur nächsten Überschrift

Praktischerweise bringt die Java-Bibliothek schon eine Model-Klasse mit, die wir direkt verwenden können. Dies ist DefaultTableModel, die ebenso eine Unterklasse von AbstractTableModel ist. Nützliche Ergänzungen sind Methoden, mit denen 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:

Listing 10.62javax/swing/table/DefaultTableModel.java, getValueAt()

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

Abbildung 10.62Obertypen von DefaultTableModel

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.

 
Zum Seitenanfang

10.19.4Ein eigener Renderer für Tabellen Zur vorigen ÜberschriftZur nächsten Ü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 Bildes. 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:

Listing 10.63javax/swing/DefaultTableCellRenderer.java, setValue()

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.

UML-Diagramm von DefaultTableCellRenderer

Abbildung 10.63UML-Diagramm von DefaultTableCellRenderer

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 10.64com/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.

[zB]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:

@Override 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(Class<?> columnClass, TableCellRenderer renderer) 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 von dem Renderer zeichnen, der mit diesem Typ verbunden ist:

Listing 10.65com/tutego/insel/ui/table/SquareTableWithRenderer.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 10.66com/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;
}
}
 
Zum Seitenanfang

10.19.5Zell-Editoren Zur vorigen ÜberschriftZur nächsten Ü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 10.67com/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(…).

 
Zum Seitenanfang

10.19.6Automatisches Sortieren und Filtern mit RowSorter * Zur vorigen ÜberschriftZur nächsten Überschrift

Nicht immer sollen alle Daten aus dem Modell 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 (etwa über einen Modell-Dekorator, der Elemente nicht durchlässt), aber das ist nicht flexibel.

[zB]Beispiel

Die einfachste Möglichkeit zum Sortieren bietet die Klasse JTable selbst mit einer Methode:

table.setAutoCreateRowSorter( true );

Allerdings müssen hier viele Sachen zusammenkommen, sodass das Standardverhalten gut funktioniert, und das ist nicht immer gegeben.

Steigen wir ein wenig tiefer ein, um mehr Kontrolle beim Filtern und Sortieren zu bekommen. Die Java-Bibliothek sieht den Typ RowSorter vor, auf den die JTable zurückgreift, um auch ohne Änderungen am eigenen Modell Elemente herauszufiltern oder Spalten zu sortieren.

Einfache Tabelle und einfaches Modell mit Potenzen

Wir wollen mit einer JTable und einem einfachem Modell beginnen. An der Stelle lässt sich ein RowSorter noch nicht erkennen, der folgt erst im nächsten Schritt:

Listing 10.68com/tutego/insel/ui/table/TableWithRowSorter.java, main() Teil 1

TableModel model = new DefaultTableModel( 100, 3 ) {
@Override public Integer getValueAt( int row, int column ) {
return (int) Math.pow( row, column + 1 );
}
};
final JTable table = new JTable( model );
JFrame frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.add( new JScrollPane(table) );
frame.pack();
frame.setVisible( true );

Das konkrete TableModel wird dem Konstruktor von JTable übergeben – es hätte auch mit setModel(…) gesetzt werden können. Bis dahin ist alles wie gehabt.

TableRowSorter und Standardsortierung

Kommt ein RowSorter ins Spiel, muss diesem das Originalmodell übergeben werden und der RowSorter der Tabelle mitgeteilt werden.

Listing 10.69com/tutego/insel/ui/table/TableWithRowSorter.java, main() Teil 2

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

Der TableRowSorter ist eine Unterklasse vom RowSorter und bekommt das ursprüngliche TableModel im Konstruktor übergeben: Starten wir das Programm, ist schon eine Sortierung eingebaut. Mit einem Klick auf die Kopfzeile der Tabellenspalte zeigt ein kleiner Pfeil die Sortierrichtung an. Die Sortierung ist allerdings auf den ersten Blick etwas verwirrend, denn sie ist auf String-Ebene, was bei Zahlen nicht gut aussieht, denn so ist etwa »19« < »2« < »20«. Woran liegt das?

Jede Spalte hat eine Column-Class, und die ist beim DefaultTableModel standardmäßig Object. TableRowSorter richtet sich zum Sortieren genau nach diesem Klassentyp. Und da bei Object nichts zu sortieren ist, ruft TableRowSorter einfach toString() auf jedem Element auf und nutzt String-Vergleiche. Für Zahlen ist das unerwünscht, und mit zwei Mitteln lässt es sich das Sortierverhalten ändern: Erstens kann unser Tabellenmodell einfach getColumnClass(…) überschreiben und Integer.class zurückgeben, wobei der TableRowSorter dann erkennt, dass die Column-Class vom Typ Comparable ist, und dann compareTo(…) für einen Vergleich nutzt. Die zweite Möglichkeit ist, einen eigenen Comparator für eine Spalte zu setzen, was ein eigenes getColumnClass(…) einspart. Zur Logik der Sortierung siehe auch das Javadoc bei der Klasse TableRowSorter.

Comparator für die Sortierung zuweisen

Da der RowSorter bei der ColumnClass Object die Inhalte als String sortiert, wollen wir einen expliziten Comparator zuweisen, der nach der Anzahl der gesetzten Bits geht:

Listing 10.70com/tutego/insel/ui/table/TableWithRowSorter.java, main() Teil 3

rowSorter.setComparator( 0, new Comparator<Integer>() {
@Override public int compare( Integer i1, Integer i2 ) {
return Integer.bitCount( i1 ) - Integer.bitCount( i2 );
}
} );

Mit welcher Logik der TableRowSorter eine Spalte sortiert, bestimmt also der Comparator, den wir über setComparator(…) gesetzt haben. Das erste Argument der Methode steht für die Spalte, für die der Comparator gesetzt ist, alle anderen Spalten behalten das Standardverhalten.

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

Listing 10.71com/tutego/insel/ui/table/TableWithRowSorter.java, main() Teil 4

rowSorter.setSortable( 1, false );

Die dritte Spalte behält weiterhin die String-Sortierung.

Filter

Auf die Ergebnismenge lassen sich Filter anwenden, die Elemente herausnehmen. Es gibt einige vordefinierte Filter, die die drei statischen Methoden von RowFilter liefern:

abstract class javax.swing.RowFilter<M,I>
  • regexFilter(String regex, int... indices)

  • dateFilter(ComparisonType type, Date date, int... indices)

  • numberFilter(ComparisonType type, Number number, int... indices)

Die Rückgaben sind RowFilter<M,I>, die direkt als Argument von setRowFilter(…) taugen; so wird ein Filter beim TableRowSorter gesetzt:

Listing 10.72TableWithRowSorter.java, main() Teil 5

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. Alle Vergleiche sind natürlich auf der textuellen Repräsentation, und die eine Frage nach Gerade und Ungerade würde in der Praxis sicherlich anders gelöst werden als mit regulären Ausdrücken, aber das Beispiel veranschaulicht die Funktionsweise.

Stellungswechsel

Wird eine Zelle in der Tabelle angeklickt und meldet ein Listener die Selektion, müssen wir darauf achten, eine Umrechnung der Positionen vorzunehmen. Denn wenn der Benutzer eine sortierte und gefilterte Tabelle sieht, stimmen die Positionen der Elemente ja überhaupt nicht mit dem originalen Modell überein.

Ein Listener zeigt das:

Listing 10.73com/tutego/insel/ui/table/TableWithRowSorter.java, main() Teil 6

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 Positionen 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, das 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 von RowSorter. Also muss gemappt werden, und das ist die 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(int viewColumnIndex). Da der RowSorter – wie der Name schon sagt – nur auf Zeilen arbeitet, ist bei ihm die Methode nicht zu finden.

 


Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

<< zurück
 Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Java SE 8 Standard-Bibliothek Java SE 8 Standard-Bibliothek
Jetzt Buch bestellen

 Buchempfehlungen
Zum Rheinwerk-Shop: Java ist auch eine Insel
Java ist auch eine Insel


Zum Rheinwerk-Shop: Professionell entwickeln mit Java EE 8
Professionell entwickeln mit Java EE 8


Zum Rheinwerk-Shop: Besser coden
Besser coden


Zum Rheinwerk-Shop: Entwurfsmuster
Entwurfsmuster


Zum Rheinwerk-Shop: IT-Projektmanagement
IT-Projektmanagement


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo

 
 


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

Cookie-Einstellungen ändern