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

Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 5 (Tiger-Release)
Buch: Java ist auch eine Insel
gp Kapitel 15 Komponenten, Container und Ereignisse
  gp 15.1 Es tut sich was – Ereignisse beim AWT
    gp 15.1.1 Was ist ein Ereignis?
    gp 15.1.2 Die Klasse AWTEvent
    gp 15.1.3 Events auf verschiedenen Ebenen
    gp 15.1.4 Ereignisquellen, -senken und Horcher (Listener)
    gp 15.1.5 Listener implementieren
    gp 15.1.6 Listener bei Ereignisauslöser anmelden/abmelden
    gp 15.1.7 Aufrufen der Listener
  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.4.3 JWindow und JDialog
  gp 15.5 Informationstext über die Klasse JLabel
    gp 15.5.1 Mehrzeiliger Text, HTML in der Darstellung
  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 Der Container JPanel
  gp 15.10 Alles Auslegungssache: die Layoutmanager
    gp 15.10.1 FlowLayout
    gp 15.10.2 BorderLayout
    gp 15.10.3 GridLayout
    gp 15.10.4 Der GridBagLayout-Manager
    gp 15.10.5 Null-Layout
    gp 15.10.6 Weitere Layoutmanager
  gp 15.11 Horizontale und vertikale Schieberegler
    gp 15.11.1 Der AdjustmentListener, der auf Änderungen hört
  gp 15.12 JSlider
  gp 15.13 Ein Auswahlmenü – Choice, JComboBox
    gp 15.13.1 ItemListener
    gp 15.13.2 Zuordnung einer Taste mit einem Eintrag
    gp 15.13.3 DateComboBox
  gp 15.14 Eines aus vielen – Kontrollfelder (JCheckBox)
    gp 15.14.1 Ereignisse über ItemListener
  gp 15.15 Kontrollfeldgruppen, Optionsfelder und JRadioButton
  gp 15.16 Der Fortschrittsbalken JProgressBar
  gp 15.17 Rahmen (Borders)
  gp 15.18 Symbolleisten alias Toolbars
  gp 15.19 Menüs
    gp 15.19.1 Die Menüleisten und die Einträge
    gp 15.19.2 Menüeinträge definieren
    gp 15.19.3 Mnemonics und Shortcuts (Accelerator)
    gp 15.19.4 Beispiel für ein Programm mit Menüleisten
    gp 15.19.5 Popup-Menüs
  gp 15.20 Das Konzept des Model-View-Controllers
  gp 15.21 List-Boxen
  gp 15.22 JSpinner
  gp 15.23 Texteingabefelder
    gp 15.23.1 Text in einer Eingabezeile
    gp 15.23.2 Die Oberklasse der JText-Komponenten: JTextComponent
    gp 15.23.3 JPasswordField
    gp 15.23.4 Validierende Eingabefelder
    gp 15.23.5 Mehrzeilige Textfelder
    gp 15.23.6 Die Editor-Klasse JEditorPane
  gp 15.24 Bäume mit JTree-Objekten
    gp 15.24.1 Selektionen bemerken
  gp 15.25 Tabellen mit JTable
    gp 15.25.1 Ein eigenes Tabellen-Model
    gp 15.25.2 AbstractTableModel
    gp 15.25.3 DefaultTableModel
    gp 15.25.4 Ein eigener Renderer für Tabellen
    gp 15.25.5 Zell-Editoren
    gp 15.25.6 Größe und Umrandung der Zellen
    gp 15.25.7 Spalteninformationen
    gp 15.25.8 Tabellenkopf von Swing-Tabellen
    gp 15.25.9 Selektionen einer Tabelle
    gp 15.25.10 Ein professionelles Tabellenlayout mit JGrid
  gp 15.26 JRootPane, JLayeredPane und JDesktopPane
    gp 15.26.1 JRootPane und JLayeredPane
    gp 15.26.2 JDesktopPane und die Kinder JInternalFrame
    gp 15.26.3 Der Farbauswahldialog JColorChooser
    gp 15.26.4 Der Dateiauswahldialog
  gp 15.27 Flexibles Java-Look&Feel
  gp 15.28 Swing-Beschriftungen einer anderen Sprache geben
  gp 15.29 Die Zwischenablage (Clipboard)
  gp 15.30 Undo durchführen
  gp 15.31 Ereignisverarbeitung auf unterster Ebene
  gp 15.32 AWT, Swing und die Threads
    gp 15.32.1 Warum Swing nicht Thread-sicher ist
    gp 15.32.2 Swing-Elemente bedienen mit invokeLater() und invokeAndWait()
  gp 15.33 Selbst definierte Cursor
    gp 15.33.1 Flackern des Mauszeigers bei Animationen vermeiden
  gp 15.34 Mausrad-Unterstützung
  gp 15.35 Benutzerinteraktionen automatisieren
    gp 15.35.1 Automatisch in die Tasten hauen
    gp 15.35.2 Mausoperationen
    gp 15.35.3 Methoden zur Zeitsteuerung
    gp 15.35.4 Screenshots
    gp 15.35.5 Funktionsweise und Beschränkungen
    gp 15.35.6 Zeitliches Ausführen mit dem javax.swing.Timer
    gp 15.35.7 MouseInfo und PointerInfo
  gp 15.36 Alternativen zu AWT und Swing
    gp 15.36.1 XML-Beschreibungen der Oberfläche: Swixml, XUL/Luxor
    gp 15.36.2 SWT


Galileo Computing

15.25 Tabellen mit JTabldowntop

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 Ä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 nimmt der Konstruktor im zweiten Argument ein Feld mit Spaltennamen an.

Abbildung
Hier klicken, um das Bild zu Vergrößern


Beispiel   Eine Tabelle mit Überschriften in einer JScrollPane

Listing 15.35   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.25.1 Ein eigenes Tabellen-Model  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 Model 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 Model, 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.
gp 

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


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

Galileo Computing

15.25.2 AbstractTableModel  downtop

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 soweit wie möglich von einer Klasse vorimplementiert wird. Daher wurde es zu einem Entwurfsmuster unter dem Namen Interface/Implementation-Pair. Die zu TableModel passende Klasse heißt AbstractTableModel und sie 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, muss 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.


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

Abbildung
Hier klicken, um das Bild zu Vergrößern

Listing 15.36   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
Hier klicken, um das Bild zu Vergrößern

Abbildung 15.24   JTable mit Model

Das Tabellen-Model 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.


Beispiel   Das QuadratTableModelSimple soll unserer Tabelle zugewiesen werden.

TableModel model = new QuadratTableModelSimple();
JTable table = new JTable();
table.setModel( model );          // oder new JTable( model )

Abbildung
Hier klicken, um das Bild zu Vergrößern

Ä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, so schreiben wir zum Beispiel


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 zuerst einmal beim Model über isCellEditable(), ob eine Zelle überhaupt vom Anwender modifiziert werden kann. Wenn das Ergebnis false ist, wird kein Editor angezeigt. Falls das Ergebnis true ist, sucht die JTable einen passenden Editor raus 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 noch einmal die JTable über getValueAt() den aktuellen Wert.


Beispiel   Über setValueAt() bekommen wir den neuen Wert als erstes Argument. Interessiert uns der alte Wert, können wir das 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 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.25.3 DefaultTableModel  downtop

Praktischeweise bringt die Java-Bibliothek schon eine Model-Klasse mitm die direkt von uns verwendet werden kann. Es ist DefaultTableModel und ebenso eine Unterklasse von AbstractTableModel. 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, 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 ein eigenes Model von AbstractTableModel ableiten.


Galileo Computing

15.25.4 Ein eigener Renderer für Tabellen  downtop

Damit eine Tabelle nicht nur die typischen Informationen in Zeichenketten darstellen muss, lässt sich ein TableCellRenderer einsetzen, mit dem 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 Standardimplementierung 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.

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

Abbildung
Hier klicken, um das Bild zu Vergrößern


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 Model 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.37   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

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 von dem Renderer zeichnen, der mit diesem Typ verbunden ist.


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

Object.class passt auf alle Zellen.

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.38   table/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;
  }
}

Galileo Computing

15.25.5 Zell-Editoren  downtop

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

Listing 15.39   table/SimpleTableCellEditor.java


package 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();

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

        return component;
    }

    public Object getCellEditorValue()
    {
        return component.getText();
    }
}

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


Galileo Computing

15.25.6 Größe und Umrandung der Zellen  downtop

Jede Zelle hat eine bestimmte Größe, die durch ihren Inhalt vorgegeben ist. Zusätzlich liegt zwischen zwei Zellen immer etwas Freiraum (engl. gap). Dieser lässt sich erfragen mit getIntercellSpacing().


Dimension d = table.getIntercellSpacing();   // d.width == 1, d.height == 1

Der JTable kann global mit setIntercellSpacing() dieser Zwischenraum (engl. margin) oben/unten und rechts/links zugewiesen werden.


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

Soll die Zelle rechts und links 2 Pixel mehr bekommen, ist gapWidth auf 4 zu setzen.

Die Gesamtgröße einer Zelle ist dann der der Margin Zeile + Zellhöhe bzw. 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. grip) lässt sich modifizieren. Die folgenden Funktionen sind angewendet auf die JTable.


setShowGrid( false ); Schaltet die Umrandung aus
setShowGrid( false ); setShowVerticalLines( true ); Zeigt nur vertikale Linien
setGridColor( Color.GRAY ); Die Umrandung wird grau


Galileo Computing

15.25.7 Spalteninformationen  downtop

Alle Zelleninformationen der Tabelle stecken im Model einer JTable. Doch Informationen über die Spalten werden nicht im TableModel abgespeichert, sondern in Objekten vom Typ TableColumn. Jede Spalte bekommt ein eigenes TableColumn-Objekt, und eine Sammlung der Objekte bildet das TableColumnModel, welches 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)enum.nextElement() );

getColumns() bezieht eine Enumeration von TableColumn-Objekten. Soll ein ganz bestimmtes TableColumn untersucht werden, so kann auch die Funktion getColumn(index) genutzt werden. Liegt ein TableColumn vor, so lässt sich von diesem die aktuelle minimale und maximale Breite setzen.


Beispiel   Ändere von der ersten Spalte die Breite auf 100 Pixel.

TableColumn col = table.getColumnModel().getColumn( 0 );
col.setPreferredWidth( 100 );


Galileo Computing

15.25.8 Tabellenkopf von Swing-Tabellen  downtop

Der Kopf (engl. header) einer JTable ist ein JTableHeader-Objekt, welches 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 ebenfalls 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 in TableColumn die Informationen selbst liegen.


Galileo Computing

15.25.9 Selektionen einer Tabelle  downtop

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


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

table.setSelectionMode( ListSelectionModel.SINGLE_INTERVAL_SELECTION );

In Abhängigkeit eines gültigen Selektionsmodus lassen sich über Methoden alle Elemente einer Spalte oder Zeile selektieren. Das Selektieren erlauben jedoch erst zwei Funktionen.


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 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&Feel blau ausgefüllt.

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


Galileo Computing

15.25.10 Ein professionelles Tabellenlayout mit JGrid  toptop

Eine JTable ist zwar eine einfache Komponente, doch schon in der HTML-Tabelle gibt es Eigenschaften, die die JTable nicht abbilden kann: Zellen, die über mehrere Zeilen und Spalten gehen. Hier ist einiges an Programmieraufwand nötig, doch unnötig für diejenigen, die auf die freie Komponente JGrid zurückgreifen. Die Swing-Komponente gehört zu Pepper (http://jeppers.sourceforge.net/), einer Komponente für Tabellenkalkulationen, die sogar eine Formelauswertung ähnlich Excel versteht. Pepper selbst liegt zwar unter der GPL, doch JGrid ist LGPL und lässt sich somit in eigenen kommerziellen Anwendungen einbinden.

<pic: JGrid.jpg >





Copyright © Galileo Press GmbH 2004
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