Mehrzeilige Tabellenzellen

Durch das Einfügen einer JTextArea können Tabellenzellen mehrzeiligen Text darstellen. Hierzu müssen der jeweilige CellEditor und CellRenderer angepasst werden.

Das Beispiel zeigt eine Tabelle, in der die mittlere von drei Spalten zur Aufnahme von mehrzeiligem Text geeignet ist. Dies wird durch das Einfügen von JTextArea-Objekten in die betroffenen Zellen erreicht.

Das graphische Interface

Die Klasse AreaTableBsp enthält die Startmethode main() und erzeugt das GUI der Anwendung in ihrer Methode initGUI(), die im Konstruktor aufgerufen wird.
Hier werden zuächst zwei String-Arrays, ein eindimensionales mit den Spaltennamen und ein zweidimensionales mit den initialen Inhalten der Tabelle erzeugt. Sie werden verwendet, um das Model vom Typ DefaultTableModel zu initialisieren. Es wird dem Konstuktor einer JTable bei deren Erzeugung übergeben. Die Tabelle wird in eine JScrollPane gesetzt, die wiederum auf dem Hauptfenster, einem JFrame, zentral dargestellt wird.
Vorher werden der Tabelle allerdings noch ein Renderer über die Methode setDefaultRenderer(), sowie ihrer zweiten Spalte ein CellEditor hinzugefügt.

import java.awt.Color;
import java.awt.Component;

import javax.swing.AbstractCellEditor;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;

public class AreaTableBsp {

    private final int areaRows = 3;
    private final int areaCols = 20;

    public AreaTableBsp() {
        initGUI();
    }

    private void initGUI() {
        String[] columnNames = { "Spalte 1", "Spalte 2", "Spalte 3" };
        String[][] content = {
                { "Text in Feld 0.0", "Dies ist ein etwas längerer Text in einer JTexArea.", "Text in Feld 0.2" },
                { "Text in Feld 1.0", "Text in Feld 1.1", "Text in Feld 1.2" },
                { "Text in Feld 2.0", "Text in Feld 2.1", "Text in Feld 2.2" } };
        DefaultTableModel model = new DefaultTableModel(content, columnNames);
        JTable table = new JTable(model);
        table.setDefaultRenderer(Object.class, new CustomRenderer(areaRows, areaCols));
        table.getColumnModel().getColumn(1).setCellEditor(new AreaEditor(areaRows, areaCols));
        JScrollPane sp = new JScrollPane(table);

        JFrame frame = new JFrame();
        frame.add(sp);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setTitle("Tabelle mit JTextArea");
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new AreaTableBsp());
    }
}

CellRenderer und CellEditor

Ein CellRenderer bestimmt die Erscheinung einer unselektierten Tabellenzelle, ein CellEditor die Erscheinung und Funktionalität der selektierten Komponente einer Zelle. Sowohl Editor, als auch Renderer können entweder als Standard der gesamten Tabelle, bzw. einem vorgegebenen Zellinhaltstyp oder aber einzelnen Spalten zugeordnet werden. Zu Demonstrationszwecken wurde hier für Renderer und Editor jeweils eines der beiden Verfahren gewählt.

In Zeile 31 wird, wie oben erwähnt, ein DefaultRenderer für alle Tabellenzellen bestimmt, die Objekte vom Typ Object enthalten. Hierzu werden der Methode setDefaultRenderer() zwei Parameter übergeben. Der erste bestimmt den Inhaltstyp der Zelle in Form eines Class-Objektes, für den dieser Renderer gelten soll. Der zweite Parameter besteht aus dem Renderer-Objekt selbst.
In Zeile 32 wird für die mittlere Spalte ein CellEditor definiert.
Sowohl dem Konstruktor des Renderers, als auch demjenigen des Editors wird jeweils die Anzahl an Reihen und Spalten für die Erzeugung der Textarea übergeben.

class CustomRenderer extends DefaultTableCellRenderer {
    private JTextArea area;
    private JScrollPane sp;

    public CustomRenderer(int rows, int cols) {
        this.area = new JTextArea(rows, cols);
        this.area.setLineWrap(true);
        this.area.setWrapStyleWord(true);
        this.sp = new JScrollPane(area);
    }

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
            int row, int column) {
        Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
        if (column == 0) {
            c.setBackground(new Color(255, 230, 200));
        }
        if (column == 2) {
            c.setBackground(new Color(230, 255, 200));
        }
        if (column == 1) {
            table.setRowHeight(row, (int) sp.getPreferredSize().getHeight());
            area.setText((String) value);
            c = sp;
        }
        return c;
    }
}

Die Renderer-Klasse erweitert hier DefaultTableCellRenderer und zeigt, wie einerseits eine JTextArea als mehrzeilige Textkomponente in Tabellenzellen eingefügt werden kann und andererseits, wie zusätzlich auch auf andere Zellen zugegriffen und im Beipiel hier deren Hintergrundfarbe geändert werden kann.

In der Klasse werden eine JTextArea und eine zugehörige JScrollPane als private Instanzvariablen deklariert und im Konstruktor initialisiert. Als einzige Methode der Klasse wird getTableCellRendererComponent() überschrieben. Sie gibt für jede Tabellenzelle die Komponente zurück, die dort gerendert werden soll. Als Parameter werden hierzu alle wesentlichen Größen übergeben, mit denen die jeweilige Erscheinung konfiguriert werden kann. U.a. können die einzelnen Tabellenzellen durch die Idices von Reihe und Spalte angesprochen werden.

JTable enthält als Standard in jeder Zelle ein JLabel. Es wird durch Aufruf der Methode der Superklasse als Component zwischengespeichert und für die erste und dritte Spalte (Indices 0 und 2) mit einer gänderten Hintergrundfarbe versehen. Die Die Label der Zellen der mittleren Spalte (Index 1) werden durch die JScrollPane mit der geladenen JTextArea ersetzt. Die Deklaration der beiden Komponenten als Instanzvariablen vermeidet hier im Gegensatz zur lokalen Deklaration, dass jedes Mal bei Aufruf der Methode ein neues Objekt erzeugt wird.

class AreaEditor extends AbstractCellEditor implements TableCellEditor {

    private JTextArea area;
    private JScrollPane sp;

    public AreaEditor(int rows, int cols) {
        this.area = new JTextArea(rows, cols);
        this.area.setLineWrap(true);
        this.area.setWrapStyleWord(true);
        this.sp = new JScrollPane(area);
    }

    @Override
    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
        area.setText((String) value);
        return sp;
    }

    @Override
    public Object getCellEditorValue() {
        return area.getText();
    }

    @Override
    public boolean stopCellEditing() {
        return true;
    }
}

Die verwendete Editor-Klasse ist von AbstractCellEditor abgeleitet und implementiert TableCellEditor Das Interface wird benötigt, um getTableCellEditorComponent() bereitzustellen. Ähnlich der Renderer-Methode getTableCellRendererComponent() gibt diese auch hier die Zellenkomponente zurück. Da der Editor jedoch lediglich für die mittlere Spalte benötigt wird, beschränkt sich dies auch auf das JScrollPane-Objekt.
Prinzipiell könnte an dieser Stelle auch von DefaultCellEditor abgeleitet werden. Da diese Klasse jedoch keinen parameterlosen Standardkonstruktor anbietet, müsste dann auf einen von drei überladenen Konstruktoren zurückgegriffen und diesem entweder ein JTextField, eine JCheckBox oder eine JComboBox übergeben werden.
Die Zellkomponenten JTextArea und JScrollPane werden auch hier als private Felder deklariert. So kann getCellEditorValue() auf die Area zugreifen, um deren Wert zurückzugeben.

Tabelle mit JTextArea in mittlerer Spalte