Following trashgod's excellent example here, I put together a little demo which accomplishes a simple task in a probably admittedly convoluted way. The GUI shown below displays a column of icons representing true/false values. If you click an icon, it changes the value to the opposite of whatever it is. Pretty much like a checkbox, but with a different appearance, and more extensible (for example, I could change it in the future to cycle through a dozen symbols rather than just two boolean symbols).
I did it by using a custom editor which is a dummy extension of a JComponent. You never even see this dummy component though, because as soon as it picks up a MousePressed
event, it causes the editor to fireEditingStopped()
. It works great, except for one weird bug I found. If you click on a symbol to change it, then move your mouse somewhere else on screen and press a keyboard key, it brings up the dummy editor in the last cell clicked (which effectively blanks out the cell), and it stays there until you either move your mouse into the cell or click a different cell.
As a hacky fix to this, I added a line in the renderer which always deselects the entire table after rendering it. This works great, and I have verified that the entire table is indeed deselected. However, despite that, if you press a keyboard key, it still executes the editor for the last edited cell. How can I prevent this behavior? I have other keyboard listeners in my application, and if no cell is selected, I do not think any of their editors should be executed.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.imageio.ImageIO;
import javax.swing.AbstractCellEditor;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
public class JTableBooleanIcons {
private JFrame frame;
private DefaultTableModel tableModel;
private JTable table;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
JTableBooleanIcons window = new JTableBooleanIcons();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the application.
*/
public JTableBooleanIcons() {
initialize();
}
/**
* Initialize the contents of the frame.
*/
private void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, 450, 400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
tableModel = new DefaultTableModel(new Object[]{"Words", "Pictures"},0);
table = new JTable(tableModel);
table.setRowHeight(40);
tableModel.addRow(new Object[]{"click icon to change", false});
tableModel.addRow(new Object[]{"click icon to change", true});
tableModel.addRow(new Object[]{"click icon to change", false});
tableModel.addRow(new Object[]{"click icon to change", true});
tableModel.addRow(new Object[]{"click icon to change", false});
tableModel.addRow(new Object[]{"click icon to change", true});
tableModel.addRow(new Object[]{"click icon to change", false});
tableModel.addRow(new Object[]{"click icon to change", true});
frame.getContentPane().add(table, BorderLayout.CENTER);
table.getColumn("Pictures").setCellRenderer(new BooleanIconRenderer());
table.getColumn("Pictures").setCellEditor(new BooleanIconEditor());
}
@SuppressWarnings("serial")
private class BooleanIconRenderer extends DefaultTableCellRenderer implements TableCellRenderer {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int col) {
String iconFilename = null;
if ((boolean) value) {
iconFilename = "yes.png";
value = true;
} else {
iconFilename = "no.png";
value = false;
}
try {
setIcon(new ImageIcon(ImageIO.read(BooleanIconRenderer.class.getResourceAsStream(iconFilename))));
} catch (Exception e) {
System.err.println("Failed to load the icon.");
}
if (isSelected) {
this.setBackground(Color.white);
} else {
this.setBackground(Color.white);
}
table.getSelectionModel().clearSelection();
return this;
}
}
@SuppressWarnings("serial")
private class BooleanIconEditor extends AbstractCellEditor implements TableCellEditor, MouseListener {
private BooleanComponent boolComp;
public BooleanIconEditor() {
boolComp = new BooleanComponent(false);
boolComp.addMouseListener(this);
}
@Override
public Object getCellEditorValue() {
return boolComp.getValue();
}
@Override
public Component getTableCellEditorComponent(JTable table,
Object value, boolean isSelected, int row, int column) {
boolComp.setValue(! (boolean) value);
return boolComp;
}
@Override
public void mouseClicked(MouseEvent e) {
this.fireEditingStopped();
}
@Override
public void mousePressed(MouseEvent e) {
this.fireEditingStopped();
}
@Override
public void mouseReleased(MouseEvent e) {
this.fireEditingStopped();
}
@Override
public void mouseEntered(MouseEvent e) {
this.fireEditingStopped();
}
@Override
public void mouseExited(MouseEvent e) {
this.fireEditingStopped();
}
}
@SuppressWarnings("serial")
private class BooleanComponent extends JComponent {
private boolean value;
public BooleanComponent(boolean value) {
this.value = value;
}
public boolean getValue() {
return value;
}
public void setValue(boolean value) {
this.value = value;
}
}
}