Das Modell und der JTable
Willemers Informatik-Ecke
Ein JTable ist ein Swing-Kontrollelement und stellt eine Tabelle zur Verfügung, wie man sie von Tabellenkalkulationen kennt. Die Tabelle wird aus einer Datenquelle gespeist, die als Modell bezeichnet wird.

Erstellen einer Tabelle

Um JTable zu benutzen, muss es importiert werden. Zu Anfang sollte also die folgende Zeile stehen:
import javax.swing.JTable;
In älteren Java-Versionen stellte JTable einen Konstruktur zur Verfügung, der String-Arrays entgegen nahm. Das funktioniert nicht mit dynamischen Daten und wird darum inzwischen nicht mehr unterstützt.

Das Modell füllt die Tabelle

Das Hauptprogramm mit JTable

Das Hauptprogramm unterscheidet sich nur geringfügig.
import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.JScrollPane;

public class TableTest extends JFrame {
    TableTest() { // Konstruktor
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JTable table = new JTable(new MeinTableModell());
        add(new JScrollPane(table)); // ohne JScrollPane keine Titel!
        setSize(400, 300);
        setVisible(true);
    }

    public static void main(String[] args) {
        new TableTest();
        // Table erzeugen
    }
}

Erweiterung von AbstractTableModel

Somit ist die Modellklasse dafür zuständig, die Daten für die Tabelle zu beschaffen, wenn sie benötigt werden.
import javax.swing.table.AbstractTableModel;


public class MeinTableModell extends AbstractTableModel {
    private DatenLieferant daten = null; // wo die Daten herkommen
    
    public MeinTableModell() {
        daten = new DatenLieferant(); // initialisiere Datenquelle
    }
    
    @Override
    public int getColumnCount() {   // Anzahl der Spalten
       return 2;
    }

    @Override
    public String getColumnName(int arg0) {    // Die Spaltenueberschriften
        if (arg0==0) return "Interpret";
        if (arg0==1) return "Titel";
        return null;
    }

    @Override
    public int getRowCount() { // Anzahl der Zeilen, also Datenobjekte
        return daten.getAnzahl();
    }

    @Override
    public Object getValueAt(int zeile, int spalte) { // Die eigentlichen Daten
        if (spalte==0) return daten.getInterpret(zeile);
        if (spalte==1) return daten.getTitel(zeile);
        return null;
    }
}

Implementierung von TableModel

Anstatt die Klasse AbstractTableModel zu erweitern könnte das Table-Modell natürlich auch das Interface TableModel implementieren. Zunächst sind die Unterschiede simpel. In den ersten Zeilen muss es heißen:

import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;

public class MeinTableModell implements TableModel {

Der Compiler wird sich melden und darauf bestehen, dass es da unimplementierten Methoden gibt. Wenn man Eclipse oder Netbeans verwendet, werden diese mit einem Mausklick mit Default-Werten erzeugt.

Die automatisch erzeugte Methode getColumnClass gibt null zurück. Das führt zu Problemen. Stattdessen String.class zurückgeben. Die Hintergründe finden Sie unter Spaltentypen.

JTable-Methoden

Im Zusammenspiel mit einem Swing-Programm werden Methoden von JTable verwendet. Als Beispiel wird in den Listings jeweils die Tabelle meinJTable verwendet.

Selektierte Zeilen in der Tabelle

Die aktuell selektierte Zeile wird über folgende Code-Sequenz ermittelt:

if (meinJTable.getSelectedRowCount()==1) {
    int zeile = meinJTable.getSelectedRow();

In der ersten Zeile wird ermittelt, ob wirklich genau eine Zeile selektiert ist. Dann wird die Zeilennummer ermittelt.

Um Zeilen zu selektieren, wird zunächst jede Selektion aufgehoben. Dann wird bei jeder gewünschten Zeile die Selektion gewechselt. Im Beispiel befindet sich in der ArrayList<Integer> eine Liste der Zeilen, die selektiert werden sollen.

meinJTable.clearSelection();
for (int i=0; i<funde.size(); i++) {
    meinJTable.changeSelection(funde.get(i), WIDTH, true,false);
}

Änderungen aktualisieren

Stellt das Programm fest, dass sich der Dateninhalt der Tabelle geändert hat, muss die Tabelle aktualisiert werden. Mit der Methode invalidate funktioniert dies nicht zuverlässig.

Der Aufruf der Methode tableChanged erwirkt ein Neuladen der Daten von JTable. Wird als Parameter null übergeben, wird alles nachgeladen. Je nach Datenquelle kann der Aufwand natürlich recht hoch sein.

meinJTable.tableChanged(null); // funktioniert, ist aber recht grob

Wird nur eine Zeile geändert, kann JTable mit dem folgenden Aufruf dazu gebracht werden, nur diese eine Zeile zu aktualisieren, deren Nummer sich in der Variablen zeile befindet.

meinJTable.tableChanged(new TableModelEvent(meinJTable.getModel(), zeile));

Die beiden folgenden Aufrufe werden verwendet, um auf das Einfügen und Löschen von Zeilen zu reagieren.

meinJTable.tableChanged(new TableModelEvent(meinJTable.getModel(), 
        0, 0, TableModelEvent.ALL_COLUMNS, TableModelEvent.INSERT ));
meinJTable.tableChanged(new TableModelEvent(meinJTable.getModel(),
        zeile[i], zeile[i], TableModelEvent.ALL_COLUMNS, TableModelEvent.DELETE ));

Eine einfache Lösung ist es, wenn man die Modell-Klasse durch den Aufruf von fireTableCellUpdated() einen Neuaufbau der Tabelle erzwingen lässt. Das kann beispielsweise in der Methode setValueAt() erfolgen.

public void setValueAt(Object value, int row, int col) {
    data[row][col] = value;
    fireTableCellUpdated(row, col);
}

Spaltentypen

Jeder Spalte kann ein Typ zugewiesen werden, indem die Methode getColumnClass überschrieben wird. Der Parameter gibt an, welche Spalte angefragt wird. Die folgende Überschreibung sorgt dafür, dass alle Spalten Strings darstellen.

@Override
public Class getColumnClass(int spaltenindex) {
    return String.class;
}

Wird für eine Spalte Integer.class zurückgegeben, wird die Spalte rechtsbündig ausgerichtet. Bei Boolean.class wird eine Checkbox angezeigt, mit einem Haken für true oder ohne Haken für false.

Die Klassen lassen sich in einem Array ablegen, deren Inhalt anhand der Spaltennummer zurückgegeben werden kann.

private final Class[] spaltenKlasse = {String.class, Integer.class, Boolean.class};
 
@Override
public Class getColumnClass(int col)
{
    return spaltenKlasse[col];
}

Editieren in der Tabelle

Das TableModel kann so eingerichtet werden, dass die Werte innerhalb der Tabelle direkt veränderbar sind. Wurde ein Spaltentyp festgelegt, wird der Standardeditor prüfen, ob die Eingabe mit dem Typ übereinstimmt. Im Fehlerfall verhindert JTable ein Verlassen des Feldes, bis die Eingabe korrekt ist.

import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
import javax.swing.JScrollPane;
import javax.swing.JFrame;
import java.awt.BorderLayout;
import java.awt.Dimension;

public class EditRender extends JFrame {

    DasTableModel myModel = new DasTableModel();

    public static void main(String[] args) {
        EditRender frame = new EditRender();
        frame.pack();
        frame.setVisible(true);
    }

    public EditRender() {
        super("EditRender");
        JTable table = new JTable(myModel);
        table.setPreferredScrollableViewportSize(new Dimension(400, 200));
        JScrollPane scrollPane = new JScrollPane(table);
        getContentPane().add(scrollPane, BorderLayout.CENTER);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    class DasTableModel extends AbstractTableModel {
        final String[] spaltenBeschriftung = { "String", "Double", "Integer", "Boole" };
        final Object[][] data = {
                { "", new Double(0.0), new Integer(0), new Boolean(false) },
                { "", new Double(0.0), new Integer(0), new Boolean(false) },
                { "", new Double(0.0), new Integer(0), new Boolean(false) },
                { "", new Double(0.0), new Integer(0), new Boolean(false) },
                { "", new Double(0.0), new Integer(0), new Boolean(false) },
        };

        public int getColumnCount() {
            return spaltenBeschriftung.length;
        }

        public int getRowCount() {
            return data.length;
        }

        public String getColumnName(int col) {
            return spaltenBeschriftung[col];
        }

        public Object getValueAt(int row, int col) {
            return data[row][col];
        }

        public Class getColumnClass(int c) {
            return getValueAt(0, c).getClass();
        }

        public boolean isCellEditable(int row, int col) {
            return true;
        }

        public void setValueAt(Object value, int row, int col) {
            data[row][col] = value;
            fireTableCellUpdated(row, col);
            System.out.println("setVal"+value); // da sind die Daten!
        }
    }
}
Interessant ist, dass die Titel der Tabelle nur erscheinen, wenn mit JScrollPane auch Schiebebalken für die Tabelle eingerichtet wurden.