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:
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;
}
}