Recently I wanted to have a clickable button in a table. Searching on Google for JButton in JTable I found a couple of suggestions, most notably this DevX article and this Esus article. There was also a StackOverflow question that just referenced other solutions. None really satisfied me. So borrowing their ideas I created my own solution.
Fundamentally, there are two problems with having a JButton in a JTable. Firstly, by default a JTable will display cell values as a String, so JButtons appear as “javax.swing.JButton”. Secondly, a JTable does not pass clicks through to the cells.
To display the buttons correctly a custom cell renderer needs to be
applied to any columns displaying JButtons. This can be done with the
code
table.getColumn("Button1").setCellRenderer(new JTableButtonRenderer())
(this assumes that there is a column in the table labelled “Button1”.
The button renderer is below. The default renderer always returns a
JLabel, but this one returns a JButton after colouring it in an
appropriate manner. Note, this renderer assumes the contents of column
will always be a JButton and will throw an exception if this is not the
case. If the table column may not contain JButtons, just extend
DefaultTableCellRenderer
, detect if the value is a JButton (or even a
Swing component) with instanceof
, otherwise call super
. This Sun
article
may help with this.
public class JTableButtonRenderer implements TableCellRenderer {
@Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
JButton button = (JButton)value;
if (isSelected) {
button.setForeground(table.getSelectionForeground());
button.setBackground(table.getSelectionBackground());
} else {
button.setForeground(table.getForeground());
button.setBackground(UIManager.getColor("Button.background"));
}
return button;
}
}
To get the table to pass clicks through to the button, add a mouse
listener to the table with
table.addMouseListener(new JTableButtonMouseListener(table))
. This
mouse listener (example below) will have to take the click, work out in
which cell it occurred and if that cell contains a JButton, click it.
public class JTableButtonMouseListener extends MouseAdapter {
private final JTable table;
public JTableButtonMouseListener(JTable table) {
this.table = table;
}
@Override public void mouseClicked(MouseEvent e) {
int column = table.getColumnModel().getColumnIndexAtX(e.getX());
int row = e.getY()/table.getRowHeight();
if (row < table.getRowCount() && row >= 0 && column < table.getColumnCount() && column >= 0) {
Object value = table.getValueAt(row, column);
if (value instanceof JButton) {
((JButton)value).doClick();
}
}
}
}
And with that, the buttons in your table should start to work. All you
need to do is return the buttons (which perform the required actions)
when requested from your model, using getValueAt()
. A complete working
example is below.
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
public class ButtonExample {
public static void main(String[] args) {
final ButtonExample example = new ButtonExample();
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
example.createAndShowGUI();
}
});
}
private void createAndShowGUI() {
JFrame frame = new JFrame("Button Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JTable table = new JTable(new JTableModel());
JScrollPane scrollPane = new JScrollPane(table);
table.setFillsViewportHeight(true);
TableCellRenderer buttonRenderer = new JTableButtonRenderer();
table.getColumn("Button1").setCellRenderer(buttonRenderer);
table.getColumn("Button2").setCellRenderer(buttonRenderer);
table.addMouseListener(new JTableButtonMouseListener(table));
frame.getContentPane().add(scrollPane, BorderLayout.CENTER);
frame.getContentPane().setPreferredSize(new Dimension(500, 200));
frame.pack();
frame.setVisible(true);
}
public static class JTableModel extends AbstractTableModel {
private static final long serialVersionUID = 1L;
private static final String[] COLUMN_NAMES = new String[] {"Id", "Stuff", "Button1", "Button2"};
private static final Class<?>[] COLUMN_TYPES = new Class<?>[] {Integer.class, String.class, JButton.class, JButton.class};
@Override public int getColumnCount() {
return COLUMN_NAMES.length;
}
@Override public int getRowCount() {
return 4;
}
@Override public String getColumnName(int columnIndex) {
return COLUMN_NAMES[columnIndex];
}
@Override public Class<?> getColumnClass(int columnIndex) {
return COLUMN_TYPES[columnIndex];
}
@Override public Object getValueAt(final int rowIndex, final int columnIndex) {
switch (columnIndex) {
case 0: return rowIndex;
case 1: return "Text for "+rowIndex;
case 2: // fall through
case 3: final JButton button = new JButton(COLUMN_NAMES[columnIndex]);
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
JOptionPane.showMessageDialog(JOptionPane.getFrameForComponent(button),
"Button clicked for row "+rowIndex);
}
});
return button;
default: return "Error";
}
}
}
private static class JTableButtonRenderer implements TableCellRenderer {
@Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
JButton button = (JButton)value;
if (isSelected) {
button.setForeground(table.getSelectionForeground());
button.setBackground(table.getSelectionBackground());
} else {
button.setForeground(table.getForeground());
button.setBackground(UIManager.getColor("Button.background"));
}
return button;
}
}
private static class JTableButtonMouseListener extends MouseAdapter {
private final JTable table;
public JTableButtonMouseListener(JTable table) {
this.table = table;
}
public void mouseClicked(MouseEvent e) {
int column = table.getColumnModel().getColumnIndexAtX(e.getX());
int row = e.getY()/table.getRowHeight();
if (row < table.getRowCount() && row >= 0 && column < table.getColumnCount() && column >= 0) {
Object value = table.getValueAt(row, column);
if (value instanceof JButton) {
((JButton)value).doClick();
}
}
}
}
}
Comments: I have removed the commenting system (for privacy concerns), below are the comments that were left on this post before doing so…
yonaides @ 2014-04-15 - Excelente ejemplo!
Lily @ 2014-07-08 - Do you have a function for the button to delete the entire row ? Instead of a messagebox?
Janemba Bokusa @ 2015-05-10 - great tuto