I've been trying to put a clickable button in a JTable, and I'm not doing great.
Im using the ButtonColumn class to do so, but the actions are ignored - the buttons are showing but nothing is happening on click.
/**
* 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. Optionally 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.
*
*/
public class ButtonColumn extends AbstractCellEditor
implements TableCellRenderer, TableCellEditor, ActionListener, MouseListener{
/**
*
*/
private static final long serialVersionUID = -6737467999355167233L;
private JTable table;
private Action action;
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 the table containing the button renderer/editor
* #param action the Action to be invoked when the button is invoked
* #param column the column to which the button renderer/editor is added
*/
public ButtonColumn(JTable table, Action action, int column)
{
this.table = table;
this.action = action;
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 );
}
// renderButton.setText( (value == null) ? "" : value.toString() );
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();
// Invoke the Action
ActionEvent event = new ActionEvent(
table,
ActionEvent.ACTION_PERFORMED,
"" + row);
action.actionPerformed(event);
}
//
// 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) {}
}
My code of actually doing so:
ButtonColumn deleteColumn = new ButtonColumn(eventTable, new AbstractAction(){
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("It works!!!!");
}
}, 3);
That never seems to be printed. I could really use some help. Thanks in advance!
EDIT: I've also tried this code and it also doesn't seem to work.
eventTable.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
JTable target = (JTable)e.getSource();
int row = target.getSelectedRow();
int column = target.getSelectedColumn();
System.out.println("Event table was clicked in row "+row+", column "+column);
}
});
Have a look at TableCellRenderer explained.
And the interface TableCellRenderer.
My guess is that the column is not editable which means the editor is never invoked so the Action is never invoked.
If this doesn't help then post a proper SSCCE demonstrating the problem.
Related
I have a jtable with a custom editor and renderer applied to a column to turn the column's contents into a Button, however once i press on any of the buttons, the table loses focus, and the only interactive object in the panel is the button that was just clicked on, which can be clicked on repeatedly.
Here is the button renderer code:
public class ButtonRenderer implements TableCellRenderer {
private Border dsd_originalBorder;
private int in_mnemonic;
private Border dsd_focusBorder;
private JButton dsd_renderButton;
/**
* empty constructor
*/
public ButtonRenderer() {
dsd_renderButton = new JButton();
}
public Component getTableCellRendererComponent(JTable dsd_table, Object dsd_value,
boolean bo_isSelected, boolean bo_hasFocus, int in_row, int in_column) {
if (!WorkplaceConstants.STR_INACTIVE.equals(dsd_table.getValueAt(in_row, dsd_table
.getColumn(Main.hm_language.get(Language.STATUS))
.getModelIndex())))
return new JLabel("");
if (bo_isSelected) {
dsd_renderButton.setForeground(dsd_table.getSelectionForeground());
dsd_renderButton.setBackground(dsd_table.getSelectionBackground());
} else {
dsd_renderButton.setForeground(dsd_table.getForeground());
dsd_renderButton.setBackground(UIManager.getColor("Button.background"));
}
if (bo_hasFocus) {
dsd_renderButton.setBorder(dsd_focusBorder);
} else {
dsd_renderButton.setBorder(dsd_originalBorder);
}
// renderButton.setText( (value == null) ? "" : value.toString() );
if (dsd_value == null) {
dsd_renderButton.setText("");
dsd_renderButton.setIcon(null);
} else if (dsd_value instanceof Icon) {
dsd_renderButton.setText("");
dsd_renderButton.setIcon((Icon) dsd_value);
} else {
dsd_renderButton.setText(dsd_value.toString());
dsd_renderButton.setIcon(null);
}
return dsd_renderButton;
}
/**
* returns the mnemonic to activate the button when the cell has focus
*
* #return the mnemonic
*/
public int m_getMnemonic() {
return in_mnemonic;
}
/**
* The mnemonic to activate the button when the cell has focus
*
* #param p_in_mnemonic
* the mnemonic
*/
public void m_setMnemonic(int p_in_mnemonic) {
this.in_mnemonic = p_in_mnemonic;
dsd_renderButton.setMnemonic(p_in_mnemonic);
}
/**
* Get foreground color of the button when the cell has focus
*
* #return the foreground color
*/
public Border m_getFocusBorder() {
return dsd_focusBorder;
}
/**
* The foreground color of the button when the cell has focus
*
* #param dsd_focusBorder
* the foreground color
*/
public void m_setFocusBorder(Border dsd_focusBorder) {
this.dsd_focusBorder = dsd_focusBorder;
}
Here is the button Editor code:
public class ButtonEditor extends AbstractCellEditor implements TableCellEditor, ActionListener, MouseListener{
private JTable dsd_table;
private int in_mnemonic;
private Border dsd_originalBorder;
private Border dsd_focusBorder;
private JButton dsd_editButton;
private Object dsd_editorValue;
private boolean bo_isButtonColumnEditor;
/**
* Constructor
* #param dsdp_table the table to which the editor is going to be applied.
* #param action the action which is to be executed when the button is clicked
*/
public ButtonEditor(JTable dsdp_table) {
this.dsd_table = dsdp_table;
dsd_editButton = new JButton();
dsd_editButton.setFocusPainted( false );
dsd_editButton.addActionListener( this );
dsd_originalBorder = dsd_editButton.getBorder();
m_setFocusBorder( new LineBorder(Color.BLUE) );
dsdp_table.addMouseListener( this );
}
public Object getCellEditorValue() {
return dsd_editorValue;
}
/**
* returns the mnemonic to activate the button when the cell has focus
*
* #return the mnemonic
*/
public int m_getMnemonic()
{
return in_mnemonic;
}
/**
* The mnemonic to activate the button when the cell has focus
*
* #param in_mnemonic the mnemonic
*/
public void m_setMnemonic(int in_mnemonic)
{
this.in_mnemonic = in_mnemonic;
dsd_editButton.setMnemonic(in_mnemonic);
}
/**
* Get foreground color of the button when the cell has focus
*
* #return the foreground color
*/
public Border m_getFocusBorder() {
return dsd_focusBorder;
}
/**
* The foreground color of the button when the cell has focus
*
* #param dsdp_focusBorder
* the foreground color
*/
public void m_setFocusBorder(Border dsdp_focusBorder) {
this.dsd_focusBorder = dsdp_focusBorder;
dsd_editButton.setBorder(dsdp_focusBorder);
}
public void actionPerformed(ActionEvent arg0) {
int in_modelRow = dsd_table.convertRowIndexToModel( dsd_table.getEditingRow() );
fireEditingStopped();
// Here i start a custom thread to run in the background.
}
public void mouseClicked(MouseEvent arg0) {
}
public void mouseEntered(MouseEvent arg0) {
}
public void mouseExited(MouseEvent arg0) {
}
public void mousePressed(MouseEvent arg0) {
if (dsd_table.isEditing() && dsd_table.getCellEditor() == this)
bo_isButtonColumnEditor = true;
}
public void mouseReleased(MouseEvent arg0) {
if (bo_isButtonColumnEditor && dsd_table.isEditing())
dsd_table.getCellEditor().stopCellEditing();
bo_isButtonColumnEditor = false;
}
public void addCellEditorListener(CellEditorListener arg0) {
}
public void cancelCellEditing() {
}
public void removeCellEditorListener(CellEditorListener arg0) {
}
public boolean shouldSelectCell(EventObject arg0) {
return false;
}
public boolean stopCellEditing() {
return false;
}
public Component getTableCellEditorComponent(JTable dsd_table, Object dsd_value,
boolean bo_isSelected, int in_row, int in_column) {
if (!WorkplaceConstants.STR_INACTIVE.equals(dsd_table.getValueAt(in_row, dsd_table
.getColumn(Main.hm_language.get(Language.STATUS))
.getModelIndex())))
return new JLabel("");
if (dsd_value == null)
{
dsd_editButton.setText( "" );
dsd_editButton.setIcon( null );
}
else if (dsd_value instanceof Icon)
{
dsd_editButton.setText( "" );
dsd_editButton.setIcon( (Icon)dsd_value );
}
else
{
dsd_editButton.setText( dsd_value.toString() );
dsd_editButton.setIcon( null );
}
bo_isButtonColumnEditor = true;
this.dsd_editorValue = dsd_value;
dsd_editButton.setBorder(dsd_originalBorder);
return dsd_editButton;
}
public boolean isCellEditable(EventObject e){
return true;
}
public Border m_getoriginalBorder() {
return dsd_originalBorder;
}
public void m_set_originalBorder(Border dsd_originalBorder) {
this.dsd_originalBorder = dsd_originalBorder;
}
}
Here is how i am assigning the editor and renderer to the table
public static void m_setButtonColumnConfiguration(JTable table) {
ButtonEditor dsd_btn_edit = new ButtonEditor(table);
dsd_btn_edit.m_setMnemonic(KeyEvent.VK_D);
table.getColumn(/*i get the identifier for the column here*/).setCellEditor(dsd_btn_edit);
ButtonRenderer dsd_btn_rend = new ButtonRenderer();
dsd_btn_rend.m_setMnemonic(KeyEvent.VK_D);
table.getColumn(/*i get the identifier for the column here*/)
.setCellRenderer(dsd_btn_rend);
}
Button, however once i press on any of the buttons, the table loses focus
Yes, anytime you click on a component it gets focus, so this makes sense.
and the only interactive object in the panel is the button that was just clicked on,
Not sure I understand this statement. You can click on the table again and it will regain focus. You can then navigate around the table.
Maybe you are just suggesting the button renderer/editor isn't working the way you expect. We can't test this because you didn't post a SSCCE.
Anyway, check out Table Button Column for another example of a button renderer/editor that responds to mouse clicks or key board events.
I have a JTable where I have inserted two buttons (delete, search) in each row.
For each button I specify the proper action in my constructor like this:
Action search = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
JTable table = (JTable) e.getSource();
int modelRow = Integer.valueOf(e.getActionCommand());
System.out.println("Search action for row: " + modelRow);
// do some processing here
// tb.searchMore(modelRow);
}
};
And then I specify to which column the button should be created like this:
// column 4 of JTable table should implement action 'search'
ButtonColumn searchButtonColumn = new ButtonColumn(table, search, 4);
searchButtonColumn.setMnemonic(KeyEvent.VK_D);
And I have a proper Button Renderer Class.
The delete button (which is being created and initialized exactly the same way) works just fine. So does a similar save button I created for another table.
The problem is with the search button. It creates the graphic button in the table, but its action is never actualluy being called. I noticed that this happens when I place it in a column other that the very first one. Any ideas for this strange behaviour? The column I am placing it is not out of bounds. Is there a problem with placing two buttons in tha same row?
Below is the Button Renderer code:
public class ButtonColumn extends AbstractCellEditor
implements TableCellRenderer, TableCellEditor, ActionListener, MouseListener {
private JTable table;
private Action action;
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 the table containing the button renderer/editor
* #param action the Action to be invoked when the button is invoked
* #param column the column to which the button renderer/editor is added
*/
public ButtonColumn(JTable table, Action action, int column) {
this.table = table;
this.action = action;
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
//
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);
}
// renderButton.setText( (value == null) ? "" : value.toString() );
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
*/
public void actionPerformed(ActionEvent e) {
int row = table.convertRowIndexToModel(table.getEditingRow());
fireEditingStopped();
// Invoke the Action
ActionEvent event = new ActionEvent(table,
ActionEvent.ACTION_PERFORMED, "" + row);
action.actionPerformed(event);
}
//
// 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.
*/
public void mousePressed(MouseEvent e) {
if (table.isEditing()
&& table.getCellEditor() == this) {
isButtonColumnEditor = true;
}
}
public void mouseReleased(MouseEvent e) {
if (isButtonColumnEditor
&& table.isEditing()) {
table.getCellEditor().stopCellEditing();
}
isButtonColumnEditor = false;
}
public void mouseClicked(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
}
I guess you forgot to set the action command to the rendered button. You rely on that command in your action. Try something like
editButton.setActionCommand(String.valueOf(column));
in your cell renderer.
I want to show a JButton within a JTable. This is nothing special, and I found a lot of examples doing this. However, I always have issues with pressing the buttons via Keyboard (not via mouse). I expect that I can select a cell and push (also visually) the button by pressing SPACE (no mnemonics).
Two snippets work like a charm, except supporting keys:
http://tips4java.wordpress.com/2009/07/12/table-button-column/
The author claims that keys work. I believe that they did, but not on all my systems I checked. However, supported mnemonics work perfectly.
(posted here: Adding Jbutton to JTable)
http://www.java2s.com/Code/Java/Swing-Components/ButtonTableExample.htm
(posted here: Adding Jbutton to JTable)
In the example, it works perfectly! However, it doesn't work for my table. Just disable row selection (I have to use cell selection), and pressing the button via key doesn't work anymore:
table.setRowSelectionAllowed(false);
I tried hard figuring out what's going wrong or how to fix it, but I failed. My only achievement is to call the action behind the button, but the button is not pressed (I mean the visual behavior).
Some information added:
I used... (in many combinations)
Ubuntu 10.04, Windows 7, Windows 8
Java 7u21, JDK 1.6.0_33, OpenJDK Runtime Environment (IcedTea6 1.8.1) (6b18-1.8.1-0ubuntu1)
WindowsLookAndFeel, Metal (Cross Platform LAF), Nimbus
0% success!
TableTest.java
import java.awt.event.ActionEvent;
import java.util.LinkedList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.AbstractTableModel;
public class TableTest extends JFrame {
public TableTest() {
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JTable table = new JTable(new TestModel());
table.setRowSelectionAllowed(false);
table.getColumnModel().getColumn(1).setPreferredWidth(3);
table.getColumnModel().getColumn(2).setPreferredWidth(3);
this.add(new JScrollPane(table));
Action increase = new AbstractAction("+") {
#Override
public void actionPerformed(ActionEvent e) {
JTable table = (JTable) e.getSource();
int row = Integer.valueOf(e.getActionCommand());
TestModel model = (TestModel) table.getModel();
model.increment(row, 0);
}
};
ButtonColumn inc = new ButtonColumn(table, increase, 1);
Action decrease = new AbstractAction("-") {
#Override
public void actionPerformed(ActionEvent e) {
JTable table = (JTable) e.getSource();
int row = Integer.valueOf(e.getActionCommand());
TestModel model = (TestModel) table.getModel();
model.decrement(row, 0);
}
};
ButtonColumn dec = new ButtonColumn(table, decrease, 2);
pack();
}
public static void main(String[] args) {
new TableTest().setVisible(true);
}
}
class TestModel extends AbstractTableModel {
List<TestRecord> records = new LinkedList<TestRecord>();
private static class TestRecord {
private int val = 0;
}
public void increment(int row, int col) {
records.get(row).val++;
fireTableCellUpdated(row, 0);
}
public void decrement(int row, int col) {
records.get(row).val--;
fireTableCellUpdated(row, 0);
}
public TestModel() {
records.add(new TestRecord());
records.add(new TestRecord());
}
#Override
public Class<?> getColumnClass(int col) {
if (col == 0) {
return Integer.class;
} else {
return ButtonColumn.class;
}
}
#Override
public boolean isCellEditable(int row, int col) {
return true;
}
#Override
public int getColumnCount() {
return 3;
}
#Override
public int getRowCount() {
return records.size();
}
#Override
public Object getValueAt(int row, int col) {
if (col == 0) {
return records.get(row).val;
} else if (col == 1) {
return "+";
} else {
return "-";
}
}
}
ButtonColumn.java
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 javax.swing.AbstractCellEditor;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.LineBorder;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumnModel;
/**
* 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. Optionally 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.
*
*/
public class ButtonColumn extends AbstractCellEditor implements
TableCellRenderer, TableCellEditor, ActionListener, MouseListener {
private JTable table;
private Action action;
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
* the table containing the button renderer/editor
* #param action
* the Action to be invoked when the button is invoked
* #param column
* the column to which the button renderer/editor is added
*/
public ButtonColumn(JTable table, Action action, int column) {
this.table = table;
this.action = action;
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);
}
// renderButton.setText( (value == null) ? "" : value.toString() );
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();
// Invoke the Action
ActionEvent event = new ActionEvent(table,
ActionEvent.ACTION_PERFORMED, "" + row);
action.actionPerformed(event);
}
//
// 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) {
}
}
The problem isn't the editor. The SPACE key stroke is not forwarded to the default editor in the first column either.
The problem is that JTable defines an Action for the SPACE key so it is intercepted before it has a chance to be passed to the editor. Search my blog for the Key Bindings entry where you will find a program that lists all the default Key Bindings for JTable. The Action that is invoked is called "addToSelection", so I'm not sure why it works differently depending on the row selection.
Anyway one solution is to remove this Action:
InputMap im = table.getInputMap(JTable.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
KeyStroke space = KeyStroke.getKeyStroke("SPACE");
im.put(space, "none");
Both TableTest, which uses ButtonColumn, and TablePopupEditor are complete examples that work correctly when the Space key is pressed in a selected button cell. Neither evinces the typical ButtonModel-defined appearance of a stand-alone button, but you can provide your own visual queues as required. This related example uses a colored border.
I have to build a complex GUI in JAVA with Swing (for the moment I have near 80 classes).
The graphic partv of the application is split as follows: a first series of tabs (eg "Management", "Administration", "Configuration"), then a second level (for example, "User", "Group", "Game"). For now I'm two grade levels (one for each level of tabs). The next level is a JPanel that manages a business object (my whole GUI is built around my business model), at this level there are 2 type of JPanel: who manages simple objects (eg, "User", "Category" , "Game", "Level") and those which manages objects "composite primary key" (eg "User_Game" which represent the form of a double-entry table for each game level for all users).
My second level of tabs can contain multiple JPanel.
When my JPanel manages a single object is composed of a JTable and two buttons (Add and Remove) on which I put events, if not it is a simple JTable. When I have foreign keys (eg "Group" for "User" and "Category" to "Game" or "Level" to "User_Game") it is a JComboBox that takes its information directly from JTableModel. When it comes to managing a JTable object to "composite primary key" the columns and rows also directly dependent models (eg "Game" and "User" "User_Game").
Each has its own JTable model that deals with the persistence layer (Hibernate for information) and other TableModel.
To manage changes (such as adding, modifying or deleting a "User") I use the code below:
import java.awt.event.*;
import javax.swing.*;
import java.beans.*;
/*
* This class listens for changes made to the data in the table via the
* TableCellEditor. When editing is started, the value of the cell is saved
* When editing is stopped the new value is saved. When the oold and new
* values are different, then the provided Action is invoked.
*
* The source of the Action is a TableCellListener instance.
*/
public class TabCellListener implements PropertyChangeListener, Runnable
{
private JTable table;
private Action action;
private int row;
private int column;
private Object oldValue;
private Object newValue;
/**
* Create a TableCellListener.
*
* #param table the table to be monitored for data changes
* #param action the Action to invoke when cell data is changed
*/
public TabCellListener(JTable table, Action action)
{
this.table = table;
this.action = action;
this.table.addPropertyChangeListener( this );
this.table.getModel().addTableModelListener(new ModelListenerTableGui(this.table, this.action));
}
/**
* Create a TableCellListener with a copy of all the data relevant to
* the change of data for a given cell.
*
* #param row the row of the changed cell
* #param column the column of the changed cell
* #param oldValue the old data of the changed cell
* #param newValue the new data of the changed cell
*/
private CellListenerTableGui(JTable table, int row, int column, Object oldValue, Object newValue)
{
this.table = table;
this.row = row;
this.column = column;
this.oldValue = oldValue;
this.newValue = newValue;
}
/**
* Get the column that was last edited
*
* #return the column that was edited
*/
public int getColumn()
{
return column;
}
/**
* Get the new value in the cell
*
* #return the new value in the cell
*/
public Object getNewValue()
{
return newValue;
}
/**
* Get the old value of the cell
*
* #return the old value of the cell
*/
public Object getOldValue()
{
return oldValue;
}
/**
* Get the row that was last edited
*
* #return the row that was edited
*/
public int getRow()
{
return row;
}
/**
* Get the table of the cell that was changed
*
* #return the table of the cell that was changed
*/
public JTable getTable()
{
return table;
}
//
// Implement the PropertyChangeListener interface
//
#Override
public void propertyChange(PropertyChangeEvent e)
{
// A cell has started/stopped editing
if ("tableCellEditor".equals(e.getPropertyName()))
{
if (table.isEditing())
processEditingStarted();
else
processEditingStopped();
}
}
/*
* Save information of the cell about to be edited
*/
private void processEditingStarted()
{
// The invokeLater is necessary because the editing row and editing
// column of the table have not been set when the "tableCellEditor"
// PropertyChangeEvent is fired.
// This results in the "run" method being invoked
SwingUtilities.invokeLater( this );
}
/*
* See above.
*/
#Override
public void run()
{
row = table.convertRowIndexToModel( table.getEditingRow() );
column = table.convertColumnIndexToModel( table.getEditingColumn() );
oldValue = table.getModel().getValueAt(row, column);
newValue = null;
}
/*
* Update the Cell history when necessary
*/
private void processEditingStopped()
{
newValue = table.getModel().getValueAt(row, column);
// The data has changed, invoke the supplied Action
if ((newValue == null && oldValue != null) || (newValue != null && !newValue.equals(oldValue)))
{
// Make a copy of the data in case another cell starts editing
// while processing this change
CellListenerTableGui tcl = new CellListenerTableGui(
getTable(), getRow(), getColumn(), getOldValue(), getNewValue());
ActionEvent event = new ActionEvent(
tcl,
ActionEvent.ACTION_PERFORMED,
"");
action.actionPerformed(event);
}
}
}
And the following action:
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeListener;
import javax.swing.Action;
public class UpdateTableListener<N> extends AbstractTableListener implements Action
{
protected boolean enabled;
public UpdateTableListener(AbstractTableGui<N> obs)
{
super(obs);
this.enabled = true;
}
#Override
public void actionPerformed(ActionEvent e)
{
if (null != e && e.getSource() instanceof CellListenerTableGui)
{
TabCellListener tcl = (TabCellListener)e.getSource();
this.obs.getModel().setValueAt(tcl.getNewValue(), tcl.getRow(), tcl.getColumn());
int sel = this.obs.getModel().getNextRequiredColumn(tcl.getRow());
if (sel == -1)
this.obs.getModel().save(tcl.getRow());
}
}
#Override
public void addPropertyChangeListener(PropertyChangeListener arg0)
{
}
#Override
public Object getValue(String arg0)
{
return null;
}
#Override
public boolean isEnabled()
{
return this.enabled;
}
#Override
public void putValue(String arg0, Object arg1)
{
}
#Override
public void removePropertyChangeListener(PropertyChangeListener arg0)
{
}
#Override
public void setEnabled(boolean arg0)
{
this.enabled = arg0;
}
}
This code works well, data are well persisted.
Then I add this code to refresh dependent components:
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeListener;
import javax.swing.Action;
public class ChangeTableListener implements Action
{
protected AbstractTableGui table;
public ChangeTableListener(AbstractTableGui table)
{
this.table = table;
}
#Override
public void actionPerformed(ActionEvent arg0)
{
this.table.getModel().fireTableDataChanged();
this.table.repaint();
}
#Override
public void addPropertyChangeListener(PropertyChangeListener arg0)
{
}
#Override
public Object getValue(String arg0)
{
return null;
}
#Override
public boolean isEnabled()
{
return false;
}
#Override
public void putValue(String arg0, Object arg1)
{
}
#Override
public void removePropertyChangeListener(PropertyChangeListener arg0)
{
}
#Override
public void setEnabled(boolean arg0)
{
}
}
My TableModel.fireTableDataChanged rebuild JTable content (calle super.fireTableDataChanged and fireTableStructureChanged), JTable.repaint reset the Renderers, and it works for Combobox (forein keys) and it update well title on double-entry tables, but it can't add or delete columns or rows on double-entry tables.
Moreover I see more high latency if there is the slightest change.
My question is simple: how do you manage inter-dependent components?
For your help,
In advance,
Thanks.
Edit :
Here an example of TableCellEditor.
import javax.swing.DefaultCellEditor;
import javax.swing.JTextField;
public class TextColumnEditor extends DefaultCellEditor
{
public TextColumnEditor()
{
super(new JTextField());
}
public boolean stopCellEditing()
{
Object v = this.getCellEditorValue();
if(v == null || v.toString().length() == 0)
{
this.fireEditingCanceled();
return false;
}
return super.stopCellEditing();
}
}
An example of TableModel :
import java.util.ArrayList;
public class GroupModelTable extends AbstractModelTable<Groups>
{
protected GroupsService service;
public GroupModelTable(AbstractTableGui<Groups> content)
{
super(content, new ArrayList<String>(), new ArrayList<Groups>());
this.headers.add("Group");
this.content.setModel(this);
this.service = new GroupsService();
this.setLines(this.service.search(new Groups()));
}
public Object getValueAt(int rowIndex, int columnIndex)
{
switch (columnIndex)
{
case 0:
return this.lines.get(rowIndex).getTitle();
default:
return "";
}
}
public void setValueAt(Object aVal, int rowIndex, int columnIndex)
{
switch (columnIndex)
{
case 0:
this.lines.get(rowIndex).setTitle(aVal.toString());
break;
default:
break;
}
}
#Override
public Groups getModel(int line, int column)
{
return null;
}
#Override
public Groups getModel(int line)
{
return this.lines.get(line);
}
public boolean isCellEditable(int row, int column)
{
return true;
}
#Override
public GroupModelTableGui newLine()
{
this.lines.add(new Groups());
return this;
}
#Override
public int getNextRequiredColumn(int row)
{
Groups g = this.getModel(row);
if (g != null && g.getTitle() != null && g.getTitle().length() > 0)
return -1;
return 0;
}
#Override
public void save(int row)
{
Groups g = this.getModel(row);
if (g != null)
{
try
{
if (g.getId() == null)
this.service.create(g);
else
this.service.update(g);
}
catch (Exception e)
{
}
}
}
#Override
public void removeRow(int row)
{
Groups g = this.getModel(row);
if (g != null)
{
try
{
if (g.getId() != null)
this.service.delete(g);
super.removeRow(row);
}
catch (Exception e)
{
}
}
}
}
An example of Table :
public class GroupTable extends AbstractTable<Groups>
{
public GroupTable()
{
new GroupModelTableGui(this);
new CellListenerTableGui(this.getContent(), new UpdateTableListenerGui<Groups>(this));
this.getContent().getColumnModel().getColumn(0).setCellEditor(new TextColumnEditorGui());
}
}
I hope it will help you to understand :/
Your TabCellListener is unfamiliar to me. Your TableCellEditor should not interact with the TableModel directly. It should implement getTableCellEditorComponent() and getCellEditorValue(), as shown in this example. When the editor concludes, the model will have the new value. Your TableModel should handle persistence. More than one view may listen to a single TableModel via TableModelListener.
Addendum: Your CellEditor, TextColumnEditor, probably shouldn't invoke fireEditingCanceled(). Pressing Escape should be sufficient to revert the edit, as shown in this example. You might also look at the related tutorial section and example.
I am making a hospital project in Java, I have made a JTable which is fetching Hospital Name and a Hospital image link i.e "Click to see more" from SQL database. My problem is that the data is successfully fetched from database to the table, but I can't click the link which is in the table cell.
How to make the link active?
Consider using JXTable (a class of SwingX): it supports a hyperlink renderer which can be configured to do any action, based on the cell value
JXTable table = new JXTable(myModel);
AbstractHyperlinkAction<Object> simpleAction = new AbstractHyperlinkAction<Object>(null) {
public void actionPerformed(ActionEvent e) {
// here goes what you want to do on activating the hyperlink
//LOG.info("hit: " + getTarget());
}
};
TableCellRenderer renderer = new DefaultTableRenderer(
new HyperlinkProvider(simpleAction));
table.getColumnExt(0).setEditable(false);
table.getColumnExt(0).setCellRenderer(renderer);
You can either make a TableCellEditor whose isCellEditable method could be used to activate on a single mouse click. Frankly, this just get messy.
Or, you could attach a MouseListener to the table directly and monitor for the mouseClicked event.
On the clicked event, you could need to getSelectedColumn and getSelectedRow to determine if they've clicked on the column you want and get the link value from the selected cell, using getValueAt
You'll need to take into consideration that the table may be sorted or the columns are no longer in the order you started them in (the user may have moved them).
Then you'll need convertColumnIndexToView and convertRowIndexToModel
Simple :D
I think that avoiding an external library is a better way to go as MadProgrammer suggests, but I guess swingx is still pure java. Anyway, I would use the mouseClicked listener and then open uri in the way described in this question How to add hyperlink in JLabel.
JTable table = new JTable();
table.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
int row = table.getSelectedRow();
int col = table.getSelectedColumn();
//build your address / link
URI uri = new URI("http: your link here");
//see below
open(uri);
}
});
//Then elsewhere as from the McDowell answer
private static void open(URI uri) {
if (Desktop.isDesktopSupported()) {
try {
Desktop.getDesktop().browse(uri);
} catch (IOException e) { /* TODO: error handling */ }
} else { /* TODO: error handling */ }
}
I solved adding a button inside the cells clickable, I followed this tutorial Table Button Column from Rob Camick:
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);
JTable table = new JTable( model );
Then:
Action delete = new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
JTable table = (JTable)e.getSource();
int modelRow = Integer.valueOf( e.getActionCommand() );
((DefaultTableModel)table.getModel()).removeRow(modelRow);
}
};
ButtonColumn buttonColumn = new ButtonColumn(table, delete, 2);
buttonColumn.setMnemonic(KeyEvent.VK_D);
Where ButtonColumn class is:
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. Optionally 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.
*
*/
public class ButtonColumn extends AbstractCellEditor
implements TableCellRenderer, TableCellEditor, ActionListener, MouseListener
{
private JTable table;
private Action action;
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 the table containing the button renderer/editor
* #param action the Action to be invoked when the button is invoked
* #param column the column to which the button renderer/editor is added
*/
public ButtonColumn(JTable table, Action action, int column)
{
this.table = table;
this.action = action;
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
//
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 );
}
// renderButton.setText( (value == null) ? "" : value.toString() );
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
*/
public void actionPerformed(ActionEvent e)
{
int row = table.convertRowIndexToModel( table.getEditingRow() );
fireEditingStopped();
// Invoke the Action
ActionEvent event = new ActionEvent(
table,
ActionEvent.ACTION_PERFORMED,
"" + row);
action.actionPerformed(event);
}
//
// 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.
*/
public void mousePressed(MouseEvent e)
{
if (table.isEditing()
&& table.getCellEditor() == this)
isButtonColumnEditor = true;
}
public void mouseReleased(MouseEvent e)
{
if (isButtonColumnEditor
&& table.isEditing())
table.getCellEditor().stopCellEditing();
isButtonColumnEditor = false;
}
public void mouseClicked(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
}