Button in einer JTable

Ich hab schon einiges im Internet gesucht, was mir noch fehlt ist, in einer JTable mit eigenem Modell den Button zum laufen zu bekommen.

Zunächst zeige ich was geht: Eine Tabelle mit eigenem ColumnModel und eigenem Renderer für verschiedenen Hintergründe. Dabei wird aber das DefaultTableModel verwendet:

Klasse ButtonColumnDemoWithColumnModel:

package stc.ui.table.button2c;

import java.awt.*;

import javax.swing.*;
import javax.swing.table.*;

import stc.ui.table.button2common.ButtonColumn;
import stc.ui.table.button2common.DifferentBackgroundsTableRenderer;
import stc.ui.table.button2common.TableDataColumnModel;

/*
 * https://tips4java.wordpress.com/2009/07/12/table-button-column/
 * https://github.com/tips4java/tips4java/blob/main/source/ButtonColumnDemo.java
 *
 * Angepasst.
 */

public class ButtonColumnDemoWithColumnModel {

    private static final Color TABLE_FOREGROUND = new Color(0, 0, 255);
    private static final Color TABLE_BACKGROUND = new Color(240, 240, 255);

    private JTable table;
    private JFrame frame;

    public void createAndShowGUI() {
        createTable();
        createFrame();
        showFrame();
    }

    private void createTable() {
        String[] columnNames = {"First Name", "Last Name", ""};
        Object[][] data =
        {
            {"Homer", "Simpson", "delete Homer"},
            {"Madge", "Simpson", "delete Madge"},
            {"Bart",  "Simpson", "delete Bart"},
            {"Lisa",  "Simpson", "delete Lisa"},
        };

        DefaultTableModel model = new DefaultTableModel(data, columnNames);
        table = new JTable();
        table.setModel(model);
        table.setDefaultRenderer(Object.class,
                new DifferentBackgroundsTableRenderer(TABLE_FOREGROUND, TABLE_BACKGROUND));
        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        table.setColumnModel(new TableDataColumnModel());

        new ButtonColumn(table, row -> delete(row), 2);
    }

    private void delete(int row) {
        Object delete = table.getModel().getValueAt(row, 2);
        Window window = SwingUtilities.windowForComponent(table);

        int result = JOptionPane.showConfirmDialog(window,
                "Are you sure you want to " + delete, "Delete Row Confirmation",
                JOptionPane.YES_NO_OPTION);

        if (result == JOptionPane.YES_OPTION) {
            ((DefaultTableModel) table.getModel()).removeRow(row);
        }
    }

    private void createFrame() {
        frame = new JFrame("Table Button Column Demo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new JScrollPane(table));
        frame.setSize(400, 160);
        frame.setLocationRelativeTo(null);
    }

    private void showFrame() {
        frame.setVisible(true);
    }

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

}

Klasse ButtonColumn:

package stc.ui.table.button2common;

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

/**
 * The ButtonColumn class provides a renderer and an editor that looks like a JButton. The renderer
 * and editor will then be used for a specified column in the table. The TableModel will contain
 * the String to be displayed on the button.
 *
 * The button can be invoked by a mouse click or by pressing the space bar when the cell has focus.
 * Optionaly a mnemonic can be set to invoke the button. When the button is invoked the provided
 * Action is invoked. The source of the Action will be the table. The action command will contain
 * the model row number of the button that was clicked.
 *
 * https://tips4java.wordpress.com/2009/07/12/table-button-column/
 * https://github.com/tips4java/tips4java/blob/main/source/ButtonColumn.java
 *
 * Deutlich verändert und angepasst von Christian Dühl.
 */

public class ButtonColumn extends AbstractCellEditor
        implements TableCellRenderer, TableCellEditor, ActionListener, MouseListener {
    private static final long serialVersionUID = 1L;

    /** Die Tabelle mit dem Button. */
    private JTable table;

    /** Wird durch den Buttonklick aufgerufen. */
    private TableButtonReactor buttonReactor;

    private int mnemonic;
    private Border originalBorder;
    private Border focusBorder;

    private JButton renderButton;
    private JButton editButton;
    private Object editorValue;
    private boolean isButtonColumnEditor;

    /**
     * Create the ButtonColumn to be used as a renderer and editor. The renderer and editor will
     * automatically be installed on the TableColumn of the specified column.
     *
     * @param table
     *            Die Tabelle mit dem Button.
     * @param buttonReactor
     *            Wird durch den Buttonklick aufgerufen.
     * @param column
     *            the column to which the button renderer/editor is added
     */
    public ButtonColumn(JTable table, TableButtonReactor buttonReactor, int column) {
        this.table = table;
        this.buttonReactor = buttonReactor;

        renderButton = new JButton();
        editButton = new JButton();
        editButton.setFocusPainted(false);
        editButton.addActionListener(this);
        originalBorder = editButton.getBorder();
        setFocusBorder(new LineBorder(Color.BLUE));

        TableColumnModel columnModel = table.getColumnModel();
        columnModel.getColumn(column).setCellRenderer(this);
        columnModel.getColumn(column).setCellEditor(this);
        table.addMouseListener(this);
    }

    /**
     * Get foreground color of the button when the cell has focus
     *
     * @return the foreground color
     */
    public Border getFocusBorder() {
        return focusBorder;
    }

    /**
     * The foreground color of the button when the cell has focus
     *
     * @param focusBorder
     *            the foreground color
     */
    public void setFocusBorder(Border focusBorder) {
        this.focusBorder = focusBorder;
        editButton.setBorder(focusBorder);
    }

    public int getMnemonic() {
        return mnemonic;
    }

    /**
     * The mnemonic to activate the button when the cell has focus
     *
     * @param mnemonic
     *            the mnemonic
     */
    public void setMnemonic(int mnemonic) {
        this.mnemonic = mnemonic;
        renderButton.setMnemonic(mnemonic);
        editButton.setMnemonic(mnemonic);
    }

    @Override
    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected,
            int row, int column) {
        if (value == null) {
            editButton.setText("");
            editButton.setIcon(null);
        }
        else if (value instanceof Icon) {
            editButton.setText("");
            editButton.setIcon((Icon) value);
        }
        else {
            editButton.setText(value.toString());
            editButton.setIcon(null);
        }

        this.editorValue = value;
        return editButton;
    }

    @Override
    public Object getCellEditorValue() {
        return editorValue;
    }

    //
    // Implement TableCellRenderer interface
    //

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
            boolean hasFocus, int row, int column) {
        if (isSelected) {
            renderButton.setForeground(table.getSelectionForeground());
            renderButton.setBackground(table.getSelectionBackground());
        }
        else {
            renderButton.setForeground(table.getForeground());
            renderButton.setBackground(UIManager.getColor("Button.background"));
        }

        if (hasFocus) {
            renderButton.setBorder(focusBorder);
        }
        else {
            renderButton.setBorder(originalBorder);
        }

        if (value == null) {
            renderButton.setText("");
            renderButton.setIcon(null);
        }
        else if (value instanceof Icon) {
            renderButton.setText("");
            renderButton.setIcon((Icon) value);
        }
        else {
            renderButton.setText(value.toString());
            renderButton.setIcon(null);
        }

        return renderButton;
    }

    //
    // Implement ActionListener interface
    //

    /**
     * The button has been pressed. Stop editing and invoke the custom Action
     */
    @Override
    public void actionPerformed(ActionEvent e) {
        int row = table.convertRowIndexToModel(table.getEditingRow());
        fireEditingStopped();

        buttonReactor.reactOnButtonClick(row);
    }

    //
    // Implement MouseListener interface
    //

    /**
     * When the mouse is pressed the editor is invoked. If you then then drag the mouse to another
     * cell before releasing it, the editor is still active. Make sure editing is stopped when the
     * mouse is released.
     */
    @Override
    public void mousePressed(MouseEvent e) {
        if (table.isEditing() && table.getCellEditor() == this)
            isButtonColumnEditor = true;
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        if (isButtonColumnEditor && table.isEditing())
            table.getCellEditor().stopCellEditing();

        isButtonColumnEditor = false;
    }

    @Override
    public void mouseClicked(MouseEvent e) {
    }

    @Override
    public void mouseEntered(MouseEvent e) {
    }

    @Override
    public void mouseExited(MouseEvent e) {
    }

}

Klasse DifferentBackgroundsTableRenderer:

package stc.ui.table.button2common;

import java.awt.Component;

import javax.swing.JTable;

import java.awt.Color;
import java.awt.Font;

import javax.swing.JLabel;
import javax.swing.table.DefaultTableCellRenderer;

/**
 * Diese Klasse ist für das Rendern (die optische Darstellung) der Tabellenzellen zuständig. Sie
 * wurde von DefaultTableCellRenderer abgeleitet, um nicht für jede Zelle eine neue Klasse zu
 * erzeugen.
 */

public class DifferentBackgroundsTableRenderer extends DefaultTableCellRenderer {

    private static final long serialVersionUID = 1L;

    public static final float FONT_SIZE = 15f;

    /** Normale Vordergrundfarbe. */
    private final Color lighterForeground;

    /** Normale Hintergrundfarbe. */
    private final Color lighterBackground;

    /** Dunklere Vordergrundfarbe. */
    private final Color darkerForeground;

    /** Dunklere Hintergrundfarbe. */
    private final Color darkerBackground;

    /**
     * Gibt an, ob bei selektierten Zeilen die Farben von Vorder- und Hintergrund vertauscht werden
     * sollen.
     */
    private boolean useSwitchedColorsForSelection;

    /**
     * Der Konstruktor, hier werden unsere Maps erzeugt.
     *
     * @param foregroundColor
     *            Vordergrundfarbe
     * @param backgroundColor
     *            Hintergrundfarbe
     */
    public DifferentBackgroundsTableRenderer(Color foregroundColor, Color backgroundColor) {
        super();

        this.lighterForeground = ColorTool.calculateLighterColor(foregroundColor);
        this.lighterBackground = ColorTool.calculateLighterColor(backgroundColor);

        this.darkerForeground = ColorTool.calculateDarkerColor(foregroundColor);
        this.darkerBackground = ColorTool.calculateDarkerColor(backgroundColor);

        useSwitchedColorsForSelection = true;
    }

    /**
     * Legt fest, dass für die Selection die gleichen Farben genutzt werden wir für die normale
     * Darstellung.
     */
    public void doNotUseSwitchedColorsForSelection() {
        useSwitchedColorsForSelection = false;
    }

    /**
     * Liefert eine Komponente für die anzuzeigende Zelle zurück. Die Hauptarbeit lassen wir
     * super.getTableCellRendererComponent(...) machen und hängen nur ein wenig Code dahinter.
     *
     * @param table
     *            Die Tabelle
     * @param value
     *            Das anzuzeigende Objekt (im Normalfall ein String)
     * @param isSelected
     *            Zeigt an, ob das Objekt selektiert wurde.
     * @param hasFocus
     *            Zeigt an, ob der Focus auf dieser Zelle liegt.
     * @param row
     *            Zeile in der die Zelle liegt.
     * @param column
     *            Spalte in der die Zelle liegt.
     */
    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
            boolean hasFocus, int row, int column) {
        JLabel createdLabel = (JLabel) super.getTableCellRendererComponent(table, value, isSelected,
                hasFocus, row, column);
        setVerticalAlignment(JLabel.CENTER);
        setHorizontalAlignment(JLabel.LEFT);


        Font oldFont = createdLabel.getFont();
        createdLabel.setFont(oldFont.deriveFont(FONT_SIZE));
        setVerticalAlignment(JLabel.CENTER);
        setHorizontalAlignment(JLabel.LEFT);

        boolean odd = row % 2 == 0;
        Color foreground = odd ? lighterForeground : darkerForeground;
        Color background = odd ? lighterBackground : darkerBackground;
        if (isSelected && useSwitchedColorsForSelection) {
            setForeground(background);
            setBackground(foreground);
        }
        else {
            setForeground(foreground);
            setBackground(background);
        }

        return createdLabel;
    }

}

Klasse TableDataColumnModel:

package stc.ui.table.button2common;

public class TableDataColumnModel extends BasicTableColumnModel {

    private static final long serialVersionUID = 1L;

    /**
     * Die Tabelle enthält die folgenden Spalten:
     *     - forename
     *     - surname
     *     - button
     *
     * @param row
     *            Zeilenindex (die erste hat die 0)
     * @param column
     *            Spaltenindex (die erste hat die 0)
     */
    public TableDataColumnModel() {
        addMinWidthColumn(100, "Vorname");
        addMinWidthColumn(100, "Nachname");
        addMinMaxWidthColumn(150, "Button");
    }

}

Klasse BasicTableColumnModel:

package stc.ui.table.button2common;

import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.TableColumn;

/**
 * Diese Klasse dient als Hilfe zur sehr viel kompakteren Erstellung von Modellen für Tabellen in
 * Swing.
 */

public class BasicTableColumnModel extends DefaultTableColumnModel {

    private static final long serialVersionUID = 1L;

    private int colNumber;

    /**
     * Fügt eine Spalte hinzu.
     *
     * @param width
     *            Breite der Spalte.
     * @param header
     *            Überschrift der Spalte.
     */
    public void addWidthColumn(int width, String header) {
        TableColumn column = createTableColumn(width, header);
        addColumn(column);
    }

    /**
     * Fügt eine Spalte hinzu, deren Breite nicht kleiner als angegeben werden kann.
     *
     * @param width
     *            Breite der Spalte, gleichzeitig auch minimale Breite.
     * @param header
     *            Überschrift der Spalte.
     */
    public void addMinWidthColumn(int width, String header) {
        addWidthColumnWithSpecialMinWidth(width, header, width);
    }

    /**
     * Fügt eine Spalte hinzu, deren Breite nicht kleiner als angegeben werden kann.
     *
     * @param width
     *            Breite der Spalte.
     * @param header
     *            Überschrift der Spalte.
     * @param minWidth
     *            Minimale Breite der Spalte.
     */
    public void addWidthColumnWithSpecialMinWidth(int width, String header, int minWidth) {
        TableColumn column = createTableColumn(width, header);
        column.setMinWidth(minWidth);
        addColumn(column);
    }

    /**
     * Fügt eine Spalte hinzu, deren Breite nicht kleiner oder größer als angegeben werden kann.
     *
     * @param width
     *            Breite der Spalte, gleichzeitig auch minimale und maximale Breite.
     * @param header
     *            Überschrift der Spalte.
     */
    public void addMinMaxWidthColumn(int width, String header) {
        addMinWidthColumnWithSpecialMaxWidth(width, header, width);
    }

    /**
     * Fügt eine Spalte hinzu, deren Breite nicht kleiner oder größer als angegeben werden kann.
     *
     * @param width
     *            Breite der Spalte, gleichzeitig auch minimale Breite.
     * @param header
     *            Überschrift der Spalte.
     * @param maxWidth
     *            Maximale Breite der Spalte.
     */
    public void addMinWidthColumnWithSpecialMaxWidth(int width, String header, int maxWidth) {
        TableColumn column = createTableColumn(width, header);
        column.setMinWidth(width);
        column.setMaxWidth(maxWidth);
        addColumn(column);
    }

    /**
     * Fügt eine Spalte hinzu, deren Breite nicht größer als angegeben werden kann.
     *
     * @param width
     *            Breite der Spalte, gleichzeitig auch maximale Breite.
     * @param header
     *            Überschrift der Spalte.
     */
    public void addMaxWidthColumn(int width, String header) {
        TableColumn column = createTableColumn(width, header);
        column.setMaxWidth(width);
        addColumn(column);
    }

    private TableColumn createTableColumn(int width, String header) {
        TableColumn column = new TableColumn(colNumber++, width);
        column.setHeaderValue(header);
        return column;
    }

}

Klasse ColorTool:

package stc.ui.table.button2common;

import java.awt.Color;

public class ColorTool {

    /**
     * Hier wird aus einer Swing-Farbe eine leicht hellere Farbe berechnet.
     *
     * @param color
     *            Farbe.
     * @return hellere Farbe.
     */
    public static Color calculateLighterColor(Color color) {
        return changeColor(color, 20);
    }

    /**
     * Hier wird aus einer Swing-Farbe eine leicht dunklere Farbe berechnet.
     *
     * @param color
     *            Farbe.
     * @return dunklere Farbe.
     */
    public static Color calculateDarkerColor(Color color) {
        return changeColor(color, -20);
    }

    /**
     * Hier wird eine Swing-Farbe um den angegebenen Betrag verändert.
     *
     * @param color
     *            Farbe.
     * @param delta
     *            Betrag um den die Farbe verändert wird.
     * @return veränderte Farbe.
     */
    public static Color changeColor(Color color, int delta) {
        return new Color(
                adjustRangeFrom0To255(color.getRed()   + delta),
                adjustRangeFrom0To255(color.getGreen() + delta),
                adjustRangeFrom0To255(color.getBlue()  + delta));
    }

    private static int adjustRangeFrom0To255(int value) {
        if (value < 0) {
            return 0;
        }
        else if (value > 255) {
            return 255;
        }
        else {
            return value;
        }
    }

}

Interface TableButtonReactor:

package stc.ui.table.button2common;

public interface TableButtonReactor {

    void reactOnButtonClick(int row);

}

Das ganze sieht dann so aus:
grafik

Falls ich Klassen zum Ausprobieren vergessen habe, reiche ich sie noch nach.

Beim Versuch nun ein eigenes TabelModel zu verwenden, scheitere ich.

Der Ansatz dazu sieht wie folgt aus:

Klasse ButtonColumnDemoWithTableDataObjects:

package stc.ui.table.button2d;

import java.util.ArrayList;
import java.util.List;

import java.awt.*;

import javax.swing.*;
import javax.swing.table.*;

import stc.ui.table.button2common.ButtonColumn;
import stc.ui.table.button2common.DifferentBackgroundsTableRenderer;
import stc.ui.table.button2common.TableDataColumnModel;

/*
 * https://tips4java.wordpress.com/2009/07/12/table-button-column/
 * https://github.com/tips4java/tips4java/blob/main/source/ButtonColumnDemo.java
 *
 * Angepasst.
 */

public class ButtonColumnDemoWithTableDataObjects {

    private static final Color TABLE_FOREGROUND = new Color(0, 0, 255);
    private static final Color TABLE_BACKGROUND = new Color(240, 240, 255);

    private List<TableData> dataSets;
    private JTable table;
    private JFrame frame;
    private DataTableModel tableModel;

    public ButtonColumnDemoWithTableDataObjects() {
        createTableData();
    }

    private void createTableData() {
        dataSets = new ArrayList<>();
        dataSets.add(new TableData("Homer", "Simpson"));
        dataSets.add(new TableData("Marge", "Simpson"));
        dataSets.add(new TableData("Bart", "Simpson"));
        dataSets.add(new TableData("Lisa", "Simpson"));
    }

    public void createAndShowGUI() {
        createTableModel();
        createTable();
        createFrame();
        showFrame();
    }

    private void createTableModel() {
        tableModel = new DataTableModel(dataSets, row -> delete(row));
    }

    private void delete(int row) {
        Object delete = table.getModel().getValueAt(row, 2);
        Window window = SwingUtilities.windowForComponent(table);

        int result = JOptionPane.showConfirmDialog(window,
                "Are you sure you want to " + delete, "Delete Row Confirmation",
                JOptionPane.YES_NO_OPTION);

        if (result == JOptionPane.YES_OPTION) {
            ((DefaultTableModel) table.getModel()).removeRow(row);
        }
    }

    private void createTable() {
        table = new JTable();
        table.setModel(tableModel);
        table.setDefaultRenderer(Object.class,
                new DifferentBackgroundsTableRenderer(TABLE_FOREGROUND, TABLE_BACKGROUND));
        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        table.setColumnModel(new TableDataColumnModel());

        new ButtonColumn(table, row -> delete(row), 2);
    }

    private void createFrame() {
        frame = new JFrame("Table Button Column Demo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new JScrollPane(table));
        frame.setSize(400, 160);
        frame.setLocationRelativeTo(null);
    }

    private void showFrame() {
        frame.setVisible(true);
    }

    /*
     * TODO
     *
     * Das funktioniert noch nicht.
     */

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

}

Klasse DataTableModel:

package stc.ui.table.button2d;

import java.util.List;

import javax.swing.JButton;
import javax.swing.table.AbstractTableModel;

import stc.ui.table.button2common.TableButtonReactor;

public class DataTableModel extends AbstractTableModel {

    private static final int NUMBER_OF_COLUMNS = 3;

    private static final long serialVersionUID = 1L;


    /** Die Liste mit den Daten der Tabelle. */
    private final List<TableData> dataSets;

    /** Wird durch den Buttonklick aufgerufen. */
    @SuppressWarnings("unused")
    private final TableButtonReactor buttonReactor;

    /**
     * Konstruktor.
     *
     * @param dataSets
     *            Die Liste mit den Daten der Tabelle.
     * @param buttonReactor
     *            Wird durch den Buttonklick aufgerufen.
     */
    public DataTableModel(List<TableData> dataSets, TableButtonReactor buttonReactor) {
        super();
        this.dataSets = dataSets;
        this.buttonReactor = buttonReactor;
    }

    /** Ermittelt die Zeilenzahl. */
    @Override
    public int getRowCount() {
        return dataSets.size();
    }

    /** Ermittelt die Spaltenzahl. */
    @Override
    public int getColumnCount() {
        return NUMBER_OF_COLUMNS;
    }

    /**
     * Ermittelt den Tabelleninhalt der angegebenen Zelle.
     *
     * Die Tabelle enthält die folgenden Spalten:
     *     - forename
     *     - surname
     *     - button
     *
     * @param row
     *            Zeilenindex (die erste hat die 0)
     * @param column
     *            Spaltenindex (die erste hat die 0)
     */
    @Override
    public Object getValueAt(int row, int column) {
        TableData data = dataSets.get(row);
        switch (column) {
            case 0:
                return data.getForename();
            case 1:
                return data.getSurname();
            case 2:
                return createButton(data);
                //return "button";
            default:
                throw new RuntimeException("Unzuläsiger Spaltenindex '" + column + "'.");
        }
    }

    private Object createButton(TableData data) {
        JButton button = new JButton();
        button.setText(data.getButtonText());
        button.addActionListener(e -> System.out.println(data));
        return button;
    }

}

Klasse TableData:

package stc.ui.table.button2d;

public class TableData {

    private final String forename;

    private final String surname;

    private final String buttonText;

    public TableData(String forename, String surname) {
        this.forename = forename;
        this.surname = surname;
        buttonText = "delete " + forename;
    }

    public String getForename() {
        return forename;
    }

    public String getSurname() {
        return surname;
    }

    public String getButtonText() {
        return buttonText;
    }

}

(Beitrag vom Verfasser gelöscht)

Das stammt ja aus dem Teil, der funktioniert. Ergo geht das.

Ein paar kleine fixes:

1.: Keine eigenen Buttons erstellen

In DataTableModel.java:

    public Object getValueAt(int row, int column) {
        TableData data = dataSets.get(row);
        switch (column) {
            case 0:
                return data.getForename();
            case 1:
                return data.getSurname();
            case 2:
                // return createButton(data); // ------------------ WEG!
                return "delete "+data.getForename(); // ----------- HIN!
                //return "button";
            default:
                throw new RuntimeException("Unzuläsiger Spaltenindex '" + column + "'.");
        }
    }

Dort soll praktisch die Button-Aufschrift zurückgegeben werden, und nicht „ein Button-Objekt“.

Darum, dass dort „ein Button ist“, kümmert sich die ButtonColumn. Die habe ich jetzt nicht komplett gelesen, aber beim Überfliegen sieht es nach dem Standard-Muster aus. Im speziellen: Dort sind keine Buttons in der Table :slight_smile: Stell’ dir vor, du hättest eine Table mit 10000 Zeilen - dort würden ja dann 10000 Buttons erstellt. Bei sowas verwendet (man in) Swing eine einzelne Komponente (z.B. einen JButton), und verwendet den „wie einen Stempel“: Intern geht Swing durch die Tabelle, und verwendet immer denselben(!) button, um den Zelleninhalt zu malen. Siehe auch How to Use Tables (The Java™ Tutorials > Creating a GUI With Swing > Using Swing Components)

2: Sagen, dass das Modell editierbar ist

In die DataTableModel.java muss

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return columnIndex == 2;
    }

Die default-Implementierung vom AbstractTableModel gibt dort pauschal false zurück. Für die Buttons muss die cell aber als „editierbar“ gelten (komplizierte Gründe, … ist halt so)

3: Kein DefaultTableModel annehmen

In der ButtonColumnDemoWithTableDataObjects.java steht

        if (result == JOptionPane.YES_OPTION) {
            ((DefaultTableModel) table.getModel()).removeRow(row);
        }

Das kracht halt beim casten, weil dein DataTableModel eben kein DefaultTableModel ist. Man kann stattdessen

        if (result == JOptionPane.YES_OPTION) {
            dataSets.remove(row);
            tableModel.fireTableDataChanged();
        }

machen. Das fireTableDataChanged ist so eine Art Schrotflinte, und besagt: Alles könnte sich geändert haben - aktualisiere mal die ganze Tabelle. (Bei großen Tabellen wäre das ineffizient, aber solange es nicht um wirklich sehr große Tabellen geht, ist das erstmal egal…)


Weitere Hinweise:

  • Alles in einen Codeblock klatschen wäre etwas einfacher für Leute, die das kopieren und schnell ausprobieren wollen
  • Sagen, was genau eigentlich nicht funktioniert (bzw. was die Frage ist), wäre gut
  • Ignorier’ coffee_eater im Zweifelsfall…
2 Likes

Offenbar ist anders als durch den vielen Text von mir beabsichtigt nicht völlig klar, was ich fragen wollte.

Der obere Teil bis zum Bild beschreibt, was bereits geht.

Daher schrieb ich oben: „Zunächst zeige ich was geht: Eine Tabelle mit eigenem ColumnModel und eigenem Renderer für verschiedenen Hintergründe.“

Ich fasse hier wie gewünscht den Code nochmal in einer Datei zusammen, das finde ich zwar sehr hässlich und auch selbst beim Rauskopieren umständlicher als einzelne Klassen, da es sich aber schon zwei Leute gewünscht haben, mache ich das mal.

Also Programm 1 - funktioniert:

package stc.ui.table.button2e;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.LineBorder;
import javax.swing.table.*;

/*
 * Siehe auch:
 * https://tips4java.wordpress.com/2009/07/12/table-button-column/
 * https://github.com/tips4java/tips4java/blob/main/source/ButtonColumnDemo.java
 */

public class TableWithButtonsWorkingExample {

    private static final Color TABLE_FOREGROUND = new Color(0, 0, 255);
    private static final Color TABLE_BACKGROUND = new Color(240, 240, 255);

    private JTable table;
    private JFrame frame;

    public void createAndShowGUI() {
        createTable();
        createFrame();
        showFrame();
    }

    private void createTable() {
        String[] columnNames = {"First Name", "Last Name", ""};
        Object[][] data =
        {
            {"Homer", "Simpson", "delete Homer"},
            {"Marge", "Simpson", "delete Marge"},
            {"Bart",  "Simpson", "delete Bart"},
            {"Lisa",  "Simpson", "delete Lisa"},
        };

        DefaultTableModel model = new DefaultTableModel(data, columnNames);
        table = new JTable();
        table.setModel(model);
        table.setDefaultRenderer(Object.class,
                new DifferentBackgroundsTableRenderer(TABLE_FOREGROUND, TABLE_BACKGROUND));
        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        table.setColumnModel(new TableDataColumnModel());

        new ButtonColumn(table, row -> delete(row), 2);
    }

    private void delete(int row) {
        Object delete = table.getModel().getValueAt(row, 2);
        Window window = SwingUtilities.windowForComponent(table);

        int result = JOptionPane.showConfirmDialog(window,
                "Are you sure you want to " + delete, "Delete Row Confirmation",
                JOptionPane.YES_NO_OPTION);

        if (result == JOptionPane.YES_OPTION) {
            ((DefaultTableModel) table.getModel()).removeRow(row);
        }
    }

    private void createFrame() {
        frame = new JFrame("Table Button Column Demo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new JScrollPane(table));
        frame.setSize(400, 160);
        frame.setLocationRelativeTo(null);
    }

    private void showFrame() {
        frame.setVisible(true);
    }

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

}

class ButtonColumn extends AbstractCellEditor
        implements TableCellRenderer, TableCellEditor, ActionListener, MouseListener {
    private static final long serialVersionUID = 1L;

    /** Die Tabelle mit dem Button. */
    private JTable table;

    /** Wird durch den Buttonklick aufgerufen. */
    private TableButtonReactor buttonReactor;

    private int mnemonic;
    private Border originalBorder;
    private Border focusBorder;

    private JButton renderButton;
    private JButton editButton;
    private Object editorValue;
    private boolean isButtonColumnEditor;

    /**
     * Create the ButtonColumn to be used as a renderer and editor. The renderer and editor will
     * automatically be installed on the TableColumn of the specified column.
     *
     * @param table
     *            Die Tabelle mit dem Button.
     * @param buttonReactor
     *            Wird durch den Buttonklick aufgerufen.
     * @param column
     *            the column to which the button renderer/editor is added
     */
    public ButtonColumn(JTable table, TableButtonReactor buttonReactor, int column) {
        this.table = table;
        this.buttonReactor = buttonReactor;

        renderButton = new JButton();
        editButton = new JButton();
        editButton.setFocusPainted(false);
        editButton.addActionListener(this);
        originalBorder = editButton.getBorder();
        setFocusBorder(new LineBorder(Color.BLUE));

        TableColumnModel columnModel = table.getColumnModel();
        columnModel.getColumn(column).setCellRenderer(this);
        columnModel.getColumn(column).setCellEditor(this);
        table.addMouseListener(this);
    }

    /**
     * Get foreground color of the button when the cell has focus
     *
     * @return the foreground color
     */
    public Border getFocusBorder() {
        return focusBorder;
    }

    /**
     * The foreground color of the button when the cell has focus
     *
     * @param focusBorder
     *            the foreground color
     */
    public void setFocusBorder(Border focusBorder) {
        this.focusBorder = focusBorder;
        editButton.setBorder(focusBorder);
    }

    public int getMnemonic() {
        return mnemonic;
    }

    /**
     * The mnemonic to activate the button when the cell has focus
     *
     * @param mnemonic
     *            the mnemonic
     */
    public void setMnemonic(int mnemonic) {
        this.mnemonic = mnemonic;
        renderButton.setMnemonic(mnemonic);
        editButton.setMnemonic(mnemonic);
    }

    @Override
    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected,
            int row, int column) {
        if (value == null) {
            editButton.setText("");
            editButton.setIcon(null);
        }
        else if (value instanceof Icon) {
            editButton.setText("");
            editButton.setIcon((Icon) value);
        }
        else {
            editButton.setText(value.toString());
            editButton.setIcon(null);
        }

        this.editorValue = value;
        return editButton;
    }

    @Override
    public Object getCellEditorValue() {
        return editorValue;
    }

    //
    // Implement TableCellRenderer interface
    //

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
            boolean hasFocus, int row, int column) {
        if (isSelected) {
            renderButton.setForeground(table.getSelectionForeground());
            renderButton.setBackground(table.getSelectionBackground());
        }
        else {
            renderButton.setForeground(table.getForeground());
            renderButton.setBackground(UIManager.getColor("Button.background"));
        }

        if (hasFocus) {
            renderButton.setBorder(focusBorder);
        }
        else {
            renderButton.setBorder(originalBorder);
        }

        if (value == null) {
            renderButton.setText("");
            renderButton.setIcon(null);
        }
        else if (value instanceof Icon) {
            renderButton.setText("");
            renderButton.setIcon((Icon) value);
        }
        else {
            renderButton.setText(value.toString());
            renderButton.setIcon(null);
        }

        return renderButton;
    }

    //
    // Implement ActionListener interface
    //

    /**
     * The button has been pressed. Stop editing and invoke the custom Action
     */
    @Override
    public void actionPerformed(ActionEvent e) {
        int row = table.convertRowIndexToModel(table.getEditingRow());
        fireEditingStopped();

        buttonReactor.reactOnButtonClick(row);
    }

    //
    // Implement MouseListener interface
    //

    /**
     * When the mouse is pressed the editor is invoked. If you then then drag the mouse to another
     * cell before releasing it, the editor is still active. Make sure editing is stopped when the
     * mouse is released.
     */
    @Override
    public void mousePressed(MouseEvent e) {
        if (table.isEditing() && table.getCellEditor() == this)
            isButtonColumnEditor = true;
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        if (isButtonColumnEditor && table.isEditing())
            table.getCellEditor().stopCellEditing();

        isButtonColumnEditor = false;
    }

    @Override
    public void mouseClicked(MouseEvent e) {
    }

    @Override
    public void mouseEntered(MouseEvent e) {
    }

    @Override
    public void mouseExited(MouseEvent e) {
    }

}

class DifferentBackgroundsTableRenderer extends DefaultTableCellRenderer {

    private static final long serialVersionUID = 1L;

    public static final float FONT_SIZE = 15f;

    /** Normale Vordergrundfarbe. */
    private final Color lighterForeground;

    /** Normale Hintergrundfarbe. */
    private final Color lighterBackground;

    /** Dunklere Vordergrundfarbe. */
    private final Color darkerForeground;

    /** Dunklere Hintergrundfarbe. */
    private final Color darkerBackground;

    /**
     * Gibt an, ob bei selektierten Zeilen die Farben von Vorder- und Hintergrund vertauscht werden
     * sollen.
     */
    private boolean useSwitchedColorsForSelection;

    /**
     * Der Konstruktor, hier werden unsere Maps erzeugt.
     *
     * @param foregroundColor
     *            Vordergrundfarbe
     * @param backgroundColor
     *            Hintergrundfarbe
     */
    public DifferentBackgroundsTableRenderer(Color foregroundColor, Color backgroundColor) {
        super();

        this.lighterForeground = ColorTool.calculateLighterColor(foregroundColor);
        this.lighterBackground = ColorTool.calculateLighterColor(backgroundColor);

        this.darkerForeground = ColorTool.calculateDarkerColor(foregroundColor);
        this.darkerBackground = ColorTool.calculateDarkerColor(backgroundColor);

        useSwitchedColorsForSelection = true;
    }

    /**
     * Legt fest, dass für die Selection die gleichen Farben genutzt werden wir für die normale
     * Darstellung.
     */
    public void doNotUseSwitchedColorsForSelection() {
        useSwitchedColorsForSelection = false;
    }

    /**
     * Liefert eine Komponente für die anzuzeigende Zelle zurück. Die Hauptarbeit lassen wir
     * super.getTableCellRendererComponent(...) machen und hängen nur ein wenig Code dahinter.
     *
     * @param table
     *            Die Tabelle
     * @param value
     *            Das anzuzeigende Objekt (im Normalfall ein String)
     * @param isSelected
     *            Zeigt an, ob das Objekt selektiert wurde.
     * @param hasFocus
     *            Zeigt an, ob der Focus auf dieser Zelle liegt.
     * @param row
     *            Zeile in der die Zelle liegt.
     * @param column
     *            Spalte in der die Zelle liegt.
     */
    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
            boolean hasFocus, int row, int column) {
        JLabel createdLabel = (JLabel) super.getTableCellRendererComponent(table, value, isSelected,
                hasFocus, row, column);
        setVerticalAlignment(JLabel.CENTER);
        setHorizontalAlignment(JLabel.LEFT);


        Font oldFont = createdLabel.getFont();
        createdLabel.setFont(oldFont.deriveFont(FONT_SIZE));
        setVerticalAlignment(JLabel.CENTER);
        setHorizontalAlignment(JLabel.LEFT);

        boolean odd = row % 2 == 0;
        Color foreground = odd ? lighterForeground : darkerForeground;
        Color background = odd ? lighterBackground : darkerBackground;
        if (isSelected && useSwitchedColorsForSelection) {
            setForeground(background);
            setBackground(foreground);
        }
        else {
            setForeground(foreground);
            setBackground(background);
        }

        return createdLabel;
    }

}

interface TableButtonReactor {

    void reactOnButtonClick(int row);

}

class TableDataColumnModel extends BasicTableColumnModel {

    private static final long serialVersionUID = 1L;

    /**
     * Die Tabelle enthält die folgenden Spalten:
     *     - forename
     *     - surname
     *     - button
     *
     * @param row
     *            Zeilenindex (die erste hat die 0)
     * @param column
     *            Spaltenindex (die erste hat die 0)
     */
    public TableDataColumnModel() {
        addMinWidthColumn(100, "Vorname");
        addMinWidthColumn(100, "Nachname");
        addMinMaxWidthColumn(150, "Button");
    }

}

class BasicTableColumnModel extends DefaultTableColumnModel {

    private static final long serialVersionUID = 1L;

    private int colNumber;

    /**
     * Fügt eine Spalte hinzu.
     *
     * @param width
     *            Breite der Spalte.
     * @param header
     *            Überschrift der Spalte.
     */
    public void addWidthColumn(int width, String header) {
        TableColumn column = createTableColumn(width, header);
        addColumn(column);
    }

    /**
     * Fügt eine Spalte hinzu, deren Breite nicht kleiner als angegeben werden kann.
     *
     * @param width
     *            Breite der Spalte, gleichzeitig auch minimale Breite.
     * @param header
     *            Überschrift der Spalte.
     */
    public void addMinWidthColumn(int width, String header) {
        addWidthColumnWithSpecialMinWidth(width, header, width);
    }

    /**
     * Fügt eine Spalte hinzu, deren Breite nicht kleiner als angegeben werden kann.
     *
     * @param width
     *            Breite der Spalte.
     * @param header
     *            Überschrift der Spalte.
     * @param minWidth
     *            Minimale Breite der Spalte.
     */
    public void addWidthColumnWithSpecialMinWidth(int width, String header, int minWidth) {
        TableColumn column = createTableColumn(width, header);
        column.setMinWidth(minWidth);
        addColumn(column);
    }

    /**
     * Fügt eine Spalte hinzu, deren Breite nicht kleiner oder größer als angegeben werden kann.
     *
     * @param width
     *            Breite der Spalte, gleichzeitig auch minimale und maximale Breite.
     * @param header
     *            Überschrift der Spalte.
     */
    public void addMinMaxWidthColumn(int width, String header) {
        addMinWidthColumnWithSpecialMaxWidth(width, header, width);
    }

    /**
     * Fügt eine Spalte hinzu, deren Breite nicht kleiner oder größer als angegeben werden kann.
     *
     * @param width
     *            Breite der Spalte, gleichzeitig auch minimale Breite.
     * @param header
     *            Überschrift der Spalte.
     * @param maxWidth
     *            Maximale Breite der Spalte.
     */
    public void addMinWidthColumnWithSpecialMaxWidth(int width, String header, int maxWidth) {
        TableColumn column = createTableColumn(width, header);
        column.setMinWidth(width);
        column.setMaxWidth(maxWidth);
        addColumn(column);
    }

    /**
     * Fügt eine Spalte hinzu, deren Breite nicht größer als angegeben werden kann.
     *
     * @param width
     *            Breite der Spalte, gleichzeitig auch maximale Breite.
     * @param header
     *            Überschrift der Spalte.
     */
    public void addMaxWidthColumn(int width, String header) {
        TableColumn column = createTableColumn(width, header);
        column.setMaxWidth(width);
        addColumn(column);
    }

    private TableColumn createTableColumn(int width, String header) {
        TableColumn column = new TableColumn(colNumber++, width);
        column.setHeaderValue(header);
        return column;
    }

}

class ColorTool {

    /**
     * Hier wird aus einer Swing-Farbe eine leicht hellere Farbe berechnet.
     *
     * @param color
     *            Farbe.
     * @return hellere Farbe.
     */
    public static Color calculateLighterColor(Color color) {
        return changeColor(color, 20);
    }

    /**
     * Hier wird aus einer Swing-Farbe eine leicht dunklere Farbe berechnet.
     *
     * @param color
     *            Farbe.
     * @return dunklere Farbe.
     */
    public static Color calculateDarkerColor(Color color) {
        return changeColor(color, -20);
    }

    /**
     * Hier wird eine Swing-Farbe um den angegebenen Betrag verändert.
     *
     * @param color
     *            Farbe.
     * @param delta
     *            Betrag um den die Farbe verändert wird.
     * @return veränderte Farbe.
     */
    public static Color changeColor(Color color, int delta) {
        return new Color(
                adjustRangeFrom0To255(color.getRed()   + delta),
                adjustRangeFrom0To255(color.getGreen() + delta),
                adjustRangeFrom0To255(color.getBlue()  + delta));
    }

    private static int adjustRangeFrom0To255(int value) {
        if (value < 0) {
            return 0;
        }
        else if (value > 255) {
            return 255;
        }
        else {
            return value;
        }
    }

}

Das sieht so aus:

grafik

Mein Problem ist, dass ich es in der realen Welt nicht mit Listen von Strings, sondern mit komplexeren Objekten zu tun habe, die tabellarisch dargestellt werden sollen (auch wenn man es TableData im Beispiel nicht ansieht). Daher möchte ich ein individuelles TableModel verwenden.

Programm 2 vereinfache ich hier mal, mir geht es gar nicht um das Löschen von Tabellenzeilen, das war das Beispiel das ich verwendet habe, wie ich es im Netz gefunden habe. Mir geht es darum, dass das ganze mit eigenem Table-Model nicht funktioniert. Hier entferne ich all die Funktionierenden Dinge von oben mal, um die Sache einfach zu halten:

package stc.ui.table.button2f;

import java.awt.Color;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.AbstractCellEditor;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.LineBorder;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumnModel;

public class TableWithButtonsAndOwnTableModleNotWorkingExample {

    private List<TableData> dataSets;
    private JTable table;
    private JFrame frame;
    private DataTableModel tableModel;

    public TableWithButtonsAndOwnTableModleNotWorkingExample() {
        createTableData();
    }

    private void createTableData() {
        dataSets = new ArrayList<>();
        dataSets.add(new TableData("Homer", "Simpson"));
        dataSets.add(new TableData("Marge", "Simpson"));
        dataSets.add(new TableData("Bart", "Simpson"));
        dataSets.add(new TableData("Lisa", "Simpson"));
    }

    public void createAndShowGUI() {
        createTableModel();
        createTable();
        createFrame();
        showFrame();
    }

    private void createTableModel() {
        tableModel = new DataTableModel(dataSets, row -> reactOnButtonClick(row));
    }

    private void reactOnButtonClick(int row) {
        TableData data = dataSets.get(row);
        System.out.println(
                "Button wurde geklickt für " + data.getForename() + " " + data.getSurname());
    }

    private void createTable() {
        table = new JTable();
        table.setModel(tableModel);

        new ButtonColumn(table, row -> reactOnButtonClick(row), 2);
    }

    private void createFrame() {
        frame = new JFrame("Table Button Column Demo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new JScrollPane(table));
        frame.setSize(400, 160);
        frame.setLocationRelativeTo(null);
    }

    private void showFrame() {
        frame.setVisible(true);
    }

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

}

class TableData {

    private final String forename;

    private final String surname;

    private final String buttonText;

    public TableData(String forename, String surname) {
        this.forename = forename;
        this.surname = surname;
        buttonText = "delete " + forename;
    }

    public String getForename() {
        return forename;
    }

    public String getSurname() {
        return surname;
    }

    public String getButtonText() {
        return buttonText;
    }

}

class DataTableModel extends AbstractTableModel {

    private static final int NUMBER_OF_COLUMNS = 3;

    private static final long serialVersionUID = 1L;


    /** Die Liste mit den Daten der Tabelle. */
    private final List<TableData> dataSets;

    /** Wird durch den Buttonklick aufgerufen. */
    @SuppressWarnings("unused")
    private final TableButtonReactor buttonReactor;

    /**
     * Konstruktor.
     *
     * @param dataSets
     *            Die Liste mit den Daten der Tabelle.
     * @param buttonReactor
     *            Wird durch den Buttonklick aufgerufen.
     */
    public DataTableModel(List<TableData> dataSets, TableButtonReactor buttonReactor) {
        super();
        this.dataSets = dataSets;
        this.buttonReactor = buttonReactor;
    }

    /** Ermittelt die Zeilenzahl. */
    @Override
    public int getRowCount() {
        return dataSets.size();
    }

    /** Ermittelt die Spaltenzahl. */
    @Override
    public int getColumnCount() {
        return NUMBER_OF_COLUMNS;
    }

    /**
     * Ermittelt den Tabelleninhalt der angegebenen Zelle.
     *
     * Die Tabelle enthält die folgenden Spalten:
     *     - forename
     *     - surname
     *     - button
     *
     * @param row
     *            Zeilenindex (die erste hat die 0)
     * @param column
     *            Spaltenindex (die erste hat die 0)
     */
    @Override
    public Object getValueAt(int row, int column) {
        TableData data = dataSets.get(row);
        switch (column) {
            case 0:
                return data.getForename();
            case 1:
                return data.getSurname();
            case 2:
                //return createButton(data);
                return "button";
            default:
                throw new RuntimeException("Unzuläsiger Spaltenindex '" + column + "'.");
        }
    }

    @SuppressWarnings("unused")
    private Object createButton(TableData data) {
        JButton button = new JButton();
        button.setText(data.getButtonText());
        button.addActionListener(e -> System.out.println(data));
        return button;
    }

}

class ButtonColumn extends AbstractCellEditor
        implements TableCellRenderer, TableCellEditor, ActionListener, MouseListener {
    private static final long serialVersionUID = 1L;

    /** Die Tabelle mit dem Button. */
    private JTable table;

    /** Wird durch den Buttonklick aufgerufen. */
    private TableButtonReactor buttonReactor;

    private int mnemonic;
    private Border originalBorder;
    private Border focusBorder;

    private JButton renderButton;
    private JButton editButton;
    private Object editorValue;
    private boolean isButtonColumnEditor;

    /**
     * Create the ButtonColumn to be used as a renderer and editor. The renderer and editor will
     * automatically be installed on the TableColumn of the specified column.
     *
     * @param table
     *            Die Tabelle mit dem Button.
     * @param buttonReactor
     *            Wird durch den Buttonklick aufgerufen.
     * @param column
     *            the column to which the button renderer/editor is added
     */
    public ButtonColumn(JTable table, TableButtonReactor buttonReactor, int column) {
        this.table = table;
        this.buttonReactor = buttonReactor;

        renderButton = new JButton();
        editButton = new JButton();
        editButton.setFocusPainted(false);
        editButton.addActionListener(this);
        originalBorder = editButton.getBorder();
        setFocusBorder(new LineBorder(Color.BLUE));

        TableColumnModel columnModel = table.getColumnModel();
        columnModel.getColumn(column).setCellRenderer(this);
        columnModel.getColumn(column).setCellEditor(this);
        table.addMouseListener(this);
    }

    /**
     * Get foreground color of the button when the cell has focus
     *
     * @return the foreground color
     */
    public Border getFocusBorder() {
        return focusBorder;
    }

    /**
     * The foreground color of the button when the cell has focus
     *
     * @param focusBorder
     *            the foreground color
     */
    public void setFocusBorder(Border focusBorder) {
        this.focusBorder = focusBorder;
        editButton.setBorder(focusBorder);
    }

    public int getMnemonic() {
        return mnemonic;
    }

    /**
     * The mnemonic to activate the button when the cell has focus
     *
     * @param mnemonic
     *            the mnemonic
     */
    public void setMnemonic(int mnemonic) {
        this.mnemonic = mnemonic;
        renderButton.setMnemonic(mnemonic);
        editButton.setMnemonic(mnemonic);
    }

    @Override
    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected,
            int row, int column) {
        if (value == null) {
            editButton.setText("");
            editButton.setIcon(null);
        }
        else if (value instanceof Icon) {
            editButton.setText("");
            editButton.setIcon((Icon) value);
        }
        else {
            editButton.setText(value.toString());
            editButton.setIcon(null);
        }

        this.editorValue = value;
        return editButton;
    }

    @Override
    public Object getCellEditorValue() {
        return editorValue;
    }

    //
    // Implement TableCellRenderer interface
    //

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
            boolean hasFocus, int row, int column) {
        if (isSelected) {
            renderButton.setForeground(table.getSelectionForeground());
            renderButton.setBackground(table.getSelectionBackground());
        }
        else {
            renderButton.setForeground(table.getForeground());
            renderButton.setBackground(UIManager.getColor("Button.background"));
        }

        if (hasFocus) {
            renderButton.setBorder(focusBorder);
        }
        else {
            renderButton.setBorder(originalBorder);
        }

        if (value == null) {
            renderButton.setText("");
            renderButton.setIcon(null);
        }
        else if (value instanceof Icon) {
            renderButton.setText("");
            renderButton.setIcon((Icon) value);
        }
        else {
            renderButton.setText(value.toString());
            renderButton.setIcon(null);
        }

        return renderButton;
    }

    //
    // Implement ActionListener interface
    //

    /**
     * The button has been pressed. Stop editing and invoke the custom Action
     */
    @Override
    public void actionPerformed(ActionEvent e) {
        int row = table.convertRowIndexToModel(table.getEditingRow());
        fireEditingStopped();

        buttonReactor.reactOnButtonClick(row);
    }

    //
    // Implement MouseListener interface
    //

    /**
     * When the mouse is pressed the editor is invoked. If you then then drag the mouse to another
     * cell before releasing it, the editor is still active. Make sure editing is stopped when the
     * mouse is released.
     */
    @Override
    public void mousePressed(MouseEvent e) {
        if (table.isEditing() && table.getCellEditor() == this)
            isButtonColumnEditor = true;
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        if (isButtonColumnEditor && table.isEditing())
            table.getCellEditor().stopCellEditing();

        isButtonColumnEditor = false;
    }

    @Override
    public void mouseClicked(MouseEvent e) {
    }

    @Override
    public void mouseEntered(MouseEvent e) {
    }

    @Override
    public void mouseExited(MouseEvent e) {
    }

}

interface TableButtonReactor {

    void reactOnButtonClick(int row);

}

Das sieht so aus:

grafik

Was hier nicht funktioniert: Der Klick auf die Buttons bleibt ohne Reaktion (reactOnButtonClick() wird nicht ausgeführt).

Vielleicht hilft auch diese Idee:

Falls das hier nicht zum Erfolg führt, schreib ich mir meine eigene Tabellenklasse - wenn dann natürlich auch viel weniger mächtig. Wäre nur schade, wenn das nötig wäre.

Edit: Die anderen Tipps schaue ich mir morgen wacher nochmal an. Ich wollte das nur nochmal klarstellen, wie es gemeint war.

Ok das hätte ich ERST probieren sollen, mit

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return columnIndex == 2;
    }

läuft es. Nochmal das ganze Programm. :smiley:

package stc.ui.table.button2g;

import java.awt.Color;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.AbstractCellEditor;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.LineBorder;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumnModel;

public class TableWithButtonsAndOwnTableModleWorkingExample {

    private List<TableData> dataSets;
    private JTable table;
    private JFrame frame;
    private DataTableModel tableModel;

    public TableWithButtonsAndOwnTableModleWorkingExample() {
        createTableData();
    }

    private void createTableData() {
        dataSets = new ArrayList<>();
        dataSets.add(new TableData("Homer", "Simpson"));
        dataSets.add(new TableData("Marge", "Simpson"));
        dataSets.add(new TableData("Bart", "Simpson"));
        dataSets.add(new TableData("Lisa", "Simpson"));
    }

    public void createAndShowGUI() {
        createTableModel();
        createTable();
        createFrame();
        showFrame();
    }

    private void createTableModel() {
        tableModel = new DataTableModel(dataSets);
    }

    private void reactOnButtonClick(int row) {
        TableData data = dataSets.get(row);
        System.out.println(
                "Button wurde geklickt für " + data.getForename() + " " + data.getSurname());
    }

    private void createTable() {
        table = new JTable();
        table.setModel(tableModel);

        new ButtonColumn(table, row -> reactOnButtonClick(row), 2);
    }

    private void createFrame() {
        frame = new JFrame("Table Button Column Demo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new JScrollPane(table));
        frame.setSize(400, 160);
        frame.setLocationRelativeTo(null);
    }

    private void showFrame() {
        frame.setVisible(true);
    }

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

}

class TableData {

    private final String forename;

    private final String surname;

    private final String buttonText;

    public TableData(String forename, String surname) {
        this.forename = forename;
        this.surname = surname;
        buttonText = "delete " + forename;
    }

    public String getForename() {
        return forename;
    }

    public String getSurname() {
        return surname;
    }

    public String getButtonText() {
        return buttonText;
    }

}

class DataTableModel extends AbstractTableModel {

    private static final int NUMBER_OF_COLUMNS = 3;

    private static final long serialVersionUID = 1L;


    /** Die Liste mit den Daten der Tabelle. */
    private final List<TableData> dataSets;

    /**
     * Konstruktor.
     *
     * @param dataSets
     *            Die Liste mit den Daten der Tabelle.
     */
    public DataTableModel(List<TableData> dataSets) {
        super();
        this.dataSets = dataSets;
    }

    /** Ermittelt die Zeilenzahl. */
    @Override
    public int getRowCount() {
        return dataSets.size();
    }

    /** Ermittelt die Spaltenzahl. */
    @Override
    public int getColumnCount() {
        return NUMBER_OF_COLUMNS;
    }

    /**
     * Ermittelt den Tabelleninhalt der angegebenen Zelle.
     *
     * Die Tabelle enthält die folgenden Spalten:
     *     - forename
     *     - surname
     *     - button
     *
     * @param row
     *            Zeilenindex (die erste hat die 0)
     * @param column
     *            Spaltenindex (die erste hat die 0)
     */
    @Override
    public Object getValueAt(int row, int column) {
        TableData data = dataSets.get(row);
        switch (column) {
            case 0:
                return data.getForename();
            case 1:
                return data.getSurname();
            case 2:
                return "button";
            default:
                throw new RuntimeException("Unzuläsiger Spaltenindex '" + column + "'.");
        }
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return columnIndex == 2;
    }

}

class ButtonColumn extends AbstractCellEditor
        implements TableCellRenderer, TableCellEditor, ActionListener, MouseListener {
    private static final long serialVersionUID = 1L;

    /** Die Tabelle mit dem Button. */
    private JTable table;

    /** Wird durch den Buttonklick aufgerufen. */
    private TableButtonReactor buttonReactor;

    private int mnemonic;
    private Border originalBorder;
    private Border focusBorder;

    private JButton renderButton;
    private JButton editButton;
    private Object editorValue;
    private boolean isButtonColumnEditor;

    /**
     * Create the ButtonColumn to be used as a renderer and editor. The renderer and editor will
     * automatically be installed on the TableColumn of the specified column.
     *
     * @param table
     *            Die Tabelle mit dem Button.
     * @param buttonReactor
     *            Wird durch den Buttonklick aufgerufen.
     * @param column
     *            the column to which the button renderer/editor is added
     */
    public ButtonColumn(JTable table, TableButtonReactor buttonReactor, int column) {
        this.table = table;
        this.buttonReactor = buttonReactor;

        renderButton = new JButton();
        editButton = new JButton();
        editButton.setFocusPainted(false);
        editButton.addActionListener(this);
        originalBorder = editButton.getBorder();
        setFocusBorder(new LineBorder(Color.BLUE));

        TableColumnModel columnModel = table.getColumnModel();
        columnModel.getColumn(column).setCellRenderer(this);
        columnModel.getColumn(column).setCellEditor(this);
        table.addMouseListener(this);
    }

    /**
     * Get foreground color of the button when the cell has focus
     *
     * @return the foreground color
     */
    public Border getFocusBorder() {
        return focusBorder;
    }

    /**
     * The foreground color of the button when the cell has focus
     *
     * @param focusBorder
     *            the foreground color
     */
    public void setFocusBorder(Border focusBorder) {
        this.focusBorder = focusBorder;
        editButton.setBorder(focusBorder);
    }

    public int getMnemonic() {
        return mnemonic;
    }

    /**
     * The mnemonic to activate the button when the cell has focus
     *
     * @param mnemonic
     *            the mnemonic
     */
    public void setMnemonic(int mnemonic) {
        this.mnemonic = mnemonic;
        renderButton.setMnemonic(mnemonic);
        editButton.setMnemonic(mnemonic);
    }

    @Override
    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected,
            int row, int column) {
        if (value == null) {
            editButton.setText("");
            editButton.setIcon(null);
        }
        else if (value instanceof Icon) {
            editButton.setText("");
            editButton.setIcon((Icon) value);
        }
        else {
            editButton.setText(value.toString());
            editButton.setIcon(null);
        }

        this.editorValue = value;
        return editButton;
    }

    @Override
    public Object getCellEditorValue() {
        return editorValue;
    }

    //
    // Implement TableCellRenderer interface
    //

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
            boolean hasFocus, int row, int column) {
        if (isSelected) {
            renderButton.setForeground(table.getSelectionForeground());
            renderButton.setBackground(table.getSelectionBackground());
        }
        else {
            renderButton.setForeground(table.getForeground());
            renderButton.setBackground(UIManager.getColor("Button.background"));
        }

        if (hasFocus) {
            renderButton.setBorder(focusBorder);
        }
        else {
            renderButton.setBorder(originalBorder);
        }

        if (value == null) {
            renderButton.setText("");
            renderButton.setIcon(null);
        }
        else if (value instanceof Icon) {
            renderButton.setText("");
            renderButton.setIcon((Icon) value);
        }
        else {
            renderButton.setText(value.toString());
            renderButton.setIcon(null);
        }

        return renderButton;
    }

    //
    // Implement ActionListener interface
    //

    /**
     * The button has been pressed. Stop editing and invoke the custom Action
     */
    @Override
    public void actionPerformed(ActionEvent e) {
        int row = table.convertRowIndexToModel(table.getEditingRow());
        fireEditingStopped();

        buttonReactor.reactOnButtonClick(row);
    }

    //
    // Implement MouseListener interface
    //

    /**
     * When the mouse is pressed the editor is invoked. If you then then drag the mouse to another
     * cell before releasing it, the editor is still active. Make sure editing is stopped when the
     * mouse is released.
     */
    @Override
    public void mousePressed(MouseEvent e) {
        if (table.isEditing() && table.getCellEditor() == this)
            isButtonColumnEditor = true;
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        if (isButtonColumnEditor && table.isEditing())
            table.getCellEditor().stopCellEditing();

        isButtonColumnEditor = false;
    }

    @Override
    public void mouseClicked(MouseEvent e) {
    }

    @Override
    public void mouseEntered(MouseEvent e) {
    }

    @Override
    public void mouseExited(MouseEvent e) {
    }

}

interface TableButtonReactor {

    void reactOnButtonClick(int row);

}

Wen es interessiert, die echte Anwendung dahinter sieht so aus:

In meinem Vokabeltrainer konnte man bisher zwar beim Abfragen und anderen Ansichten die Aussprache abspielen lassen, aber nicht in der Listenansicht. Dank @Marco13 geht es nun auch dort. Und es fehlte nur so wenig.

Danke schön!

1 Like

Vielleicht kann das auch noch helfen:
https://wiki.byte-welt.net/wiki/JTable_mit_Buttons_in_Tabellenzellen

1 Like

Buttons (oder allgemein JComponents) in einer JTable sind eigentlich etwas, was recht häufig gesucht wird. Und die Implementierung der „Renderer“ und „Editor“-Funktionalität kann ein bißchen tricky sein. Vielleicht versuche ich bei Gelegenheit mal, das irgendwo in https://github.com/javagl/CommonUI/tree/master/src/main/java/de/javagl/common/ui/table unterzubringen. (Ggf. gibt’s dann wieder ein update in Common und CommonUI - nur ein paar Utility-Klassen - #4 von Marco13 :smiley: )

@L-ectron-X Die besagte Renderer/Editor funktionalität findet man sicher tausendfach im web. Die auf https://wiki.byte-welt.net/wiki/JTable_mit_Buttons_in_Tabellenzellen sieht beim ersten Drüberscrollen recht aufgeräumt und generisch aus - mit (ein paar trivialen Sachen, die man glattziehen könnte, aber) nur einer Sache, wo man genauer schauen muss - nämlich, wie gut das ganze passt wenn man nicht direkt eine JTable erstellt, sondern der auch noch sein eigenes TableModel unterjubeln will.

Falls das gut geht, eine etwas allgemeine Frage: Gibt es irgendeine Form von „Lizenz“ für die Wiki-Snippets? Ggf. würde ich Teile davon (mit leichten Anpassungen) unter MIT-Lizenz in die CommonUI packen, mit einem Hinweis irgendwo oben in den JavaDocs wie

 /**
  * ... 
  *
  * Based on https://wiki.byte-welt.net/wiki/JTable_mit_Buttons_in_Tabellenzellen 
  */

und nehme mal an, dass das „genug“ wäre.

Sofern nicht anders angegeben, wurden die Beiträge im Wiki unter der GNU Free Document License veröffentlicht. In wie fern die mit der MIT-License kompatibel ist, habe ich nicht nachgeforscht.