Java Swing: JTextField doesn't lose focus as expected - java

I have a JTable which has 2 columns. One of these columns is represented by a JTextField and the other one by a radio button.
The model is populated in this way:
model.addRow(new Object[]{radioButton, ""});
Associated with the JTextField there is a cell editor like this:
class MyCellEditor extends DefaultCellEditor {
MyCellEditor(JTextField textField) {
super(textField);
textField.addFocusListener(new FocusListener() {
public void focusLost(FocusEvent e) {
// do something if focus is lost
}
#Override
public void focusGained(FocusEvent e) {
}
});
}
When I click on the JTextField cell I get a "blinking" cursor as expected so I can input my text in. Anyway if I click anywhere else in the main window I would expect that "focusLost(...)" method has been called but that happens only if I "play" a bit around in the window (like clicking in and out the jtextfield a few times).
Why the component doesn't lose the focus just after the first click to another external component?

you can override stopEditing() in the TableCellEditor
or write directly
table.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
more complicated (JFormattedTextField) example
import java.awt.Component;
import java.awt.EventQueue;
import java.text.DecimalFormat;
import java.text.ParseException;
import javax.swing.*;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumnModel;
public class EditorAsRendererTableTest {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JTable table = new JTable(3, 2);
TableColumnModel colModel = table.getColumnModel();
MyCellEditor both = new MyCellEditor();
colModel.getColumn(0).setCellEditor(both);
colModel.getColumn(0).setCellRenderer(both);
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
private static class MyCellEditor extends AbstractCellEditor implements TableCellEditor, TableCellRenderer {
private static final long serialVersionUID = 1L;
private JFormattedTextField renderer = new JFormattedTextField(DecimalFormat.getInstance());
private JFormattedTextField editor;
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
renderer.setValue(value);
return renderer;
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
editor = new JFormattedTextField(DecimalFormat.getInstance());
editor.setValue(value);
return editor;
}
#Override
public boolean stopCellEditing() {
try {
editor.commitEdit();
} catch (ParseException e) {
return false;
}
return super.stopCellEditing();
}
#Override
public Object getCellEditorValue() {
return editor.getValue();
}
}
private EditorAsRendererTableTest() {
}
}

I fixed like this:
1) Giving the focus to the new JTextField:
if (editCellAt(getRowCount()-1, 1)) getEditorComponent().requestFocus();
2) Table auto-detects lost focus:
table.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
3) in "MyCellEditor class --> #Override public boolean stopCellEditing()" just check if the component whether has the focus or not:
getComponent().isFocusOwner()

Related

How to add a button inside a table cell in java netbeans? (I am using the drag and drop method.)

I am trying to add a button inside a table cell. I am using the drag and drop method of netbeans since I know nothing about coding and will appreciate if you can teach me to code it. Thanks!
If you are using drag&drop in netbean for swing,
I highly advise you to touch the fundamental of swings , get your hands dirty so that you will know what is going on and how does the code work.
let me run through how you can achieve this. it will consist of 3 classes so that you will have a better understanding on what is going on and it practices oop too but of cause you can modify it to your preferred design pattern.
_main.java
public class _main extends JFrame{
private static final long serialVersionUID = 1L;
// Create new JFrame
_main(){
new JFrame("Main");
setLayout(new BorderLayout());
setSize(500,300);
add(new JLabel("Table Example ", SwingUtilities.CENTER) , BorderLayout.PAGE_START);
// ---------------- Call the method you have created in tableView.java ------------
add(new JScrollPane(new tableView(this).sampleTable()), BorderLayout.CENTER);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
public static void main(String[] args){
//Run Program
new _main();
}
}
tableView.java
public class tableView {
JFrame frame = new JFrame();
public tableView(JFrame frame) {
this.frame = frame;
}
//Create columnTitle & Table Model
String[] columnTitle = { "Data 1", "Data 2", "Data 3", "Buttons " };
DefaultTableModel model = new DefaultTableModel(columnTitle, 0);
public JTable sampleTable(){
JTable _dataTable = new JTable(model) {
#Override
public void updateUI() {
super.updateUI();
setRowHeight(34);
setAutoCreateRowSorter(true);
//------------ Placing button at your desired column ------------
TableColumn column;
column = getColumnModel().getColumn(3);
column.setCellRenderer(new tableModel(frame).new viewRenderer());
column.setCellEditor(new tableModel(frame).new ButtonsEditorView(this));
}
};
DefaultTableCellRenderer centerRenderer = new DefaultTableCellRenderer();
centerRenderer.setHorizontalAlignment(JLabel.CENTER);
//-------- Adding data to your table row , use for loop for multiple data ---------
model.addRow(new Object[]{"1","2","3"});
return _dataTable;
}
}
tableModel.java
public class tableModel extends tableView{
public tableModel(JFrame frame) {
super(frame);
}
class viewButton extends JPanel {
public JButton viewbtnp = new JButton("View");
protected viewButton() {
setOpaque(true);
setFocusable(false);
add(viewbtnp);
}
}
class viewRenderer implements TableCellRenderer {
private final viewButton panel = new viewButton() {
#Override
public void updateUI() {
super.updateUI();
setName("Table.cellRenderer");
}
};
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
int row, int column) {
panel.setBackground(isSelected ? table.getSelectionBackground() : table.getBackground());
return panel;
}
}
class ViewAction extends AbstractAction {
private final JTable table;
protected ViewAction(JTable table) {
super("view");
this.table = table;
}
#Override
public void actionPerformed(ActionEvent e) {
//--------------------------- Create your own function on what you want the button to do when button is clicked -------------
System.out.println("Clicked ");
}
}
class ButtonsEditorView extends AbstractCellEditor implements TableCellEditor {
protected final viewButton panel = new viewButton();
protected final JTable table;
protected ButtonsEditorView(JTable table) {
super();
this.table = table;
panel.viewbtnp.setAction(new ViewAction(table));
}
#Override
public Component getTableCellEditorComponent(JTable tbl, Object value, boolean isSelected, int row,
int column) {
panel.setBackground(tbl.getSelectionBackground());
return panel;
}
#Override
public Object getCellEditorValue() {
return "";
}
}
}
Output
Hope it helps.
Cheers

How do I make Java jtables, cell editors and undo work together w/o creating extranious undo event?

In the following code, I create a jtable with a custom cell editor for the first column and then add undo capabilities to the table. When you run the program, the program allows you to change the values in the first column (test by appending a "d" and then an "e" to the "abc" already there). Now enter control-z (undo) and enter control-z again. It works as expected. But now enter control-z (undo) again. This time the "abc" is erased. It looks like the swing system is setting the initial value of the column and creating an undo event for that action which the user can then undo. My question - how do I write my code so that the user only can undo the actions the user makes?
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.DefaultCellEditor;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JRootPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;
import javax.swing.undo.UndoableEdit;
public class UndoExample extends JFrame {
private static final long serialVersionUID = 1L;;
static Boolean objEnableUndoRedoActions = true;
UndoExample rootFrame;
public UndoExample() {
// This procedure starts the whole thing off.
//Create table
final String[] tableColumns = {"Column 1", "Column 2"};
JTable tabUndoExample = new JTable(
new DefaultTableModel(null, tableColumns) {
private static final long serialVersionUID = 1L;
});
final DefaultTableModel tabUndoExampleModel = (DefaultTableModel) tabUndoExample
.getModel();
tabUndoExampleModel.addRow(new Object[]{"abc", true});
tabUndoExampleModel.addRow(new Object[]{"zyw", false});
// Create the undo/redo manager
UndoManager objUndoManager = new UndoManager();
// Create a cell editor
JTextField tfTabField = new JTextField();
TableCellEditor objEditor = new DefaultCellEditor(tfTabField);
// Make the cell editor the default editor for this table's first column
tabUndoExample.getColumnModel().getColumn(0)
.setCellEditor(objEditor);
// Create the undo action on the field's document for the column
tfTabField.getDocument().addUndoableEditListener(
new uelUndoRedoTableCellField(objUndoManager, tabUndoExample));
// Allow undo and redo to be entered by the user
UndoRedoSetKeys(this, "Example", objUndoManager);
tabUndoExample.setInheritsPopupMenu(true);
//Add the table to the frame and show the frame
this.add(tabUndoExample);
this.pack();
setLocationRelativeTo(null);
}
public static void main(final String[] args) {
// Launches the application. This is required syntax.
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {
final UndoExample rootFrame = new UndoExample();
rootFrame.setVisible(true);
} catch (final Exception e) {
}
}
});
}
#SuppressWarnings("serial")
static class aueUndoRedoTableCellField extends AbstractUndoableEdit {
// Wrap the text edit action item as we need to add the table
// row and column information. This code is invoked when the
// code sees an undo event created and then later when the
// user requests an undo/redo.
JTable objTable = null;
UndoableEdit objUndoableEdit;
int objCol = -1;
int objRow = -1;
public aueUndoRedoTableCellField(UndoableEdit undoableEdit,
JTable table, int row, int col) {
super();
objUndoableEdit = undoableEdit;
objTable = table;
objCol = col;
objRow = row;
}
public void redo() throws CannotRedoException {
// When the user enters redo (or undo), this code sets
// that we are doing an redo (or undo), sets the cursor
// to the right location, and then does the undo (or redo)
// to the table cell.
UndoRedoManagerSetEnabled(false);
super.redo();
#SuppressWarnings("unused")
boolean success = objTable.editCellAt(objRow, objCol);
objTable.changeSelection(objRow, objCol, false, false);
objUndoableEdit.redo();
UndoRedoManagerSetEnabled(true);
}
public void undo() throws CannotUndoException {
super.undo();
UndoRedoManagerSetEnabled(false);
#SuppressWarnings("unused")
boolean success = objTable.editCellAt(objRow, objCol);
objTable.changeSelection(objRow, objCol, false, false);
objUndoableEdit.undo();
UndoRedoManagerSetEnabled(true);
}
}
static class aUndoRedo extends AbstractAction {
// This code is bound to the undo/redo keystrokes and tells
// Java what commands to run when the keys are later entered
// by the user.
private static final long serialVersionUID = 1L;
Boolean objUndo = true;
UndoManager objUndoManager = null;
final String objLocation;
public aUndoRedo(Boolean Undo, UndoManager undoManager, String location) {
super();
objUndo = Undo;
objUndoManager = undoManager;
objLocation = location;
}
#Override
public void actionPerformed(ActionEvent ae) {
try {
// See if operation allowed
if (!objUndoManager.canUndo() && objUndo
|| !objUndoManager.canRedo() && !objUndo)
return;
UndoRedoManagerSetEnabled(false);
if (objUndo) {
objUndoManager.undo();
} else {
objUndoManager.redo();
}
UndoRedoManagerSetEnabled(true);
// Catch errors and let user know
} catch (Exception e) {
UndoRedoManagerSetEnabled(true);
}
}
}
static class uelUndoRedoTableCellField implements UndoableEditListener {
// This action is called when the user changes the table's
// text cell. It saves the change for later undo/redo.
private UndoManager objUndoManager = null;
private JTable objTable = null;
public uelUndoRedoTableCellField(UndoManager undoManager,
JTable table) {
objUndoManager = undoManager;
objTable = table;
}
#Override
public void undoableEditHappened(UndoableEditEvent e) {
// Remember the edit but only if the code isn't doing
// an undo or redo currently.
if (UndoRedoManagerIsEnabled()) {
objUndoManager.addEdit(new aueUndoRedoTableCellField(e
.getEdit(), objTable, objTable.getSelectedRow(),
objTable.getSelectedColumn()));
}
}
}
static public Boolean UndoRedoManagerIsEnabled() {
// See if we are currently doing an undo/redo.
// Return true if so.
return objEnableUndoRedoActions;
}
static public void UndoRedoManagerSetEnabled(Boolean state) {
// Set the state of whether we are in undo/redo code.
objEnableUndoRedoActions = state;
}
static void UndoRedoSetKeys(JFrame frame, final String location, UndoManager undoManager) {
// Allow undo and redo to be called via these keystrokes for this dialog
final String cntl_y = "CNTL_Y";
final KeyStroke ksCntlY = KeyStroke.getKeyStroke("control Y");
final String cntl_z = "CNTL_Z";
final KeyStroke ksCntlZ = KeyStroke.getKeyStroke("control Z");
JRootPane root = frame.getRootPane();
root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(ksCntlZ, cntl_z);
root.getActionMap().put(
cntl_z,
new aUndoRedo(true, undoManager, location));
root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(ksCntlY, cntl_y);
root.getActionMap().put(
cntl_y,
new aUndoRedo(false, undoManager, location));
}
}
When you press a key, a series of things occur. The JTable, process the key stroke, it checks to see if the cell is editable (as the TableModel), it then asks the editor for the currently selected cell if the event should edit the cell (CellEditor#isCellEditable(EventObject)).
If this method returns true, the editor is prepared, the value from the TableModel is applied to the editor (ie setText is called), and the editor is added to the JTable, finally, the event which triggered the edit mode is re-dispatched to the editor, in your case the Ctrl+Z, which then triggers and undo event, returning the editor it's initial state (before setText was called).
You can try and use something like...
TableCellEditor objEditor = new DefaultCellEditor(tfTabField) {
#Override
public boolean isCellEditable(EventObject anEvent) {
boolean isEditable = super.isCellEditable(anEvent); //To change body of generated methods, choose Tools | Templates.
if (isEditable && anEvent instanceof KeyEvent) {
KeyEvent ke = (KeyEvent) anEvent;
if (ke.isControlDown() && ke.getKeyCode() == KeyEvent.VK_Z) {
isEditable = false;
}
}
return isEditable;
}
};
to prevent the JTable from been placed into edit when a specific key stroke occurs
Updated
So based on Andrew's answer from JTextArea setText() & UndoManager, I devised a "configurable" UndoableEditListener which can be set to ignore undoable actions, for example...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.Document;
import javax.swing.text.PlainDocument;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;
public class FixedField {
public static void main(String[] args) {
new FixedField();
}
public FixedField() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public static class UndoableEditHandler implements UndoableEditListener {
private static final int MASK
= Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
private UndoManager undoManager = new UndoManager();
private boolean canUndo = true;
public UndoableEditHandler(JTextField field) {
Document doc = field.getDocument();
doc.addUndoableEditListener(this);
field.getActionMap().put("Undo", new AbstractAction("Undo") {
#Override
public void actionPerformed(ActionEvent evt) {
try {
if (undoManager.canUndo()) {
undoManager.undo();
}
} catch (CannotUndoException e) {
System.out.println(e);
}
}
});
field.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, MASK), "Undo");
field.getActionMap().put("Redo", new AbstractAction("Redo") {
#Override
public void actionPerformed(ActionEvent evt) {
try {
if (undoManager.canRedo()) {
undoManager.redo();
}
} catch (CannotRedoException e) {
System.out.println(e);
}
}
});
field.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Y, MASK), "Redo");
}
#Override
public void undoableEditHappened(UndoableEditEvent e) {
if (canUndo()) {
undoManager.addEdit(e.getEdit());
}
}
public void setCanUndo(boolean canUndo) {
this.canUndo = canUndo;
}
public boolean canUndo() {
return canUndo;
}
}
public class TestPane extends JPanel {
public TestPane() {
JTextField field = new JTextField(10);
UndoableEditHandler handler = new UndoableEditHandler(field);
handler.setCanUndo(false);
field.setText("Help");
handler.setCanUndo(true);
add(field);
}
}
}
Now, you're going to have to devices your own TableCellEditor to support this, for example...
public static class MyCellEditor extends AbstractCellEditor implements TableCellEditor {
private JTextField editor;
private UndoableEditHandler undoableEditHandler;
public MyCellEditor(JTextField editor) {
this.editor = editor;
undoableEditHandler = new UndoableEditHandler(editor);
editor.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
fireEditingStopped();
}
});
}
#Override
public Object getCellEditorValue() {
return editor.getText();
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
undoableEditHandler.setCanUndo(false);
editor.setText(value == null ? null : value.toString());
undoableEditHandler.setCanUndo(true);
return editor;
}
}

Changing Dropdown content in a JTable Column in Swing

I have a JTable in which the first column contains combobox with same items in each cell.If i select an item in a cell combobox i need to remove the selected item from all the other combobox in that column and also add the previous selected item to all the other combobox.How should i do that?Please help me with an example.
public class Save extends JFrame {
String[] items1 = new String[] { "Cash", "Bank1", "Bank2" ,"Bank3"};
TableCellEditor editors;
DefaultTableModel dtmFunds;
private JComboBox comboBox1;
private JTable jtblFunds;
private void loadTable(){
comboBox1=new JComboBox(items1);
comboBox1.addItemListener(new ItemListener() {
#Override
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
int x=comboBox1.getSelectedIndex();
comboItem= e.getItem().toString();
}
}
});
editors=new DefaultCellEditor(comboBox1);
dtmFunds = new DefaultTableModel(new Object[][] {{"", " ","delete"}}, new Object[] {"Fund Store", "Amount","Action"});
jtblFunds=new JTable(dtmFunds){
public TableCellEditor getCellEditor(int row,int column){
int modelColumn=convertColumnIndexToModel(column);
if(modelColumn==0 && row<jtblFunds.getRowCount()-1)
return editors;
else
return super.getCellEditor(row,column);
}
};
jtblFunds.setModel(dtmFunds);
jtblFunds.getModel().addTableModelListener(new TableModelListener() {
#Override
public void tableChanged(TableModelEvent e) {
int row=e.getFirstRow();
int column=e.getColumn();
if((column==0)&&(row<jtblFunds.getRowCount()-1)){
System.out.println("Dropdown changed "+row);
}
}
});
}
}
These are are codes i have used to add combobox to JTable column named "Fund Store".
Really, focus your efforts within the CellEditor itself, that's it's job. There is no need to extend from JTable or screw around with it.
import java.awt.Component;
import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractCellEditor;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
public class TestCellEditor {
public static void main(String[] args) {
new TestCellEditor();
}
public TestCellEditor() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
List<String> values = new ArrayList<>(5);
values.add("Bananas");
values.add("Apples");
values.add("Oranages");
values.add("Grapes");
values.add("Pears");
ComboBoxTableCellEditor editor = new ComboBoxTableCellEditor(values);
DefaultTableModel model = new DefaultTableModel(new Object[]{"Fruit"}, 5);
JTable table = new JTable(model);
table.getColumnModel().getColumn(0).setCellEditor(editor);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class ComboBoxTableCellEditor extends AbstractCellEditor implements TableCellEditor {
private JComboBox editor;
private List<String> masterValues;
public ComboBoxTableCellEditor(List<String> masterValues) {
this.editor = new JComboBox();
this.masterValues = masterValues;
}
#Override
public Object getCellEditorValue() {
return editor.getSelectedItem();
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
DefaultComboBoxModel model = new DefaultComboBoxModel(masterValues.toArray(new String[masterValues.size()]));
for (int index = 0; index < table.getRowCount(); index++) {
if (index != row) {
String cellValue = (String) table.getValueAt(index, 0);
model.removeElement(cellValue);
}
}
editor.setModel(model);
editor.setSelectedItem(value);
return editor;
}
}
}
I'd prefer to have to two pools of values, one which is the master list and one which is the selected values, it would be easier and faster to prepare the editor each time it's invoked, but this is the basic idea...
All you need in fact is to update model of the comboBox you use for the editor.
comboBox1.setModel(new DefaultComboBoxModel(theArrayWithRemovedValue));
You have base array of combobox items (source) and result array of items where the selected one should be removed.
Just recreate the array removing value of the cell.
BTW. It's better to define cell editor standard way rather than overriding getCellEditor() method of JTable. Use e.g. public void setDefaultEditor(Class<?> columnClass, TableCellEditor editor). You can define a custom class for the column 0.

Previously selected JTable cell triggers editor on key press, even when explicitly deselected

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;
}
}
}
The reason for your problem is that you are actually not rendering anything within the editor. You're relying on the fact that the user is only ever going to "click" the cell to change it's value. I'd consider this a little short-sighted, but, it's you program.
To fix it immediately, simply add...
#Override
public boolean isCellEditable(EventObject e) {
return (e instanceof MouseEvent);
}
To you BooleanIconEditor.
On a side note.
In you cell renderer, you shouldn't be loading images. These should have been pre-cached as part of the constructor or even better, as static field variables. You may be doing this, but just in case.
Updated
While I'm on the subject. You should avoid changing the state of the table from within the renderer. This is really unsafe and could end you up in an infinite loop of hell as the table tries to re-render the changes you've made, again and again...
If you really want to hide the selection (I'm not sure why you would), you could either set the table's selection to match the tables background color OR make it a transparent color. Don't forget to change the selection foreground as well ;)
Update #2
Example with keyboard support ;) - couldn't resist...
public class JTableBooleanIcons {
private JFrame frame;
private DefaultTableModel tableModel;
private JTable table;
private ImageIcon yesIcon;
private ImageIcon noIcon;
/**
* 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() {
try {
yesIcon = (new ImageIcon(ImageIO.read(BooleanIconRenderer.class.getResourceAsStream("/yes.png"))));
noIcon = (new ImageIcon(ImageIO.read(BooleanIconRenderer.class.getResourceAsStream("/no.png"))));
} catch (Exception e) {
e.printStackTrace();
}
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 {
public BooleanIconRenderer() {
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int col) {
super.getTableCellRendererComponent(table, null, isSelected, hasFocus, row, col);
if ((boolean) value) {
setIcon(yesIcon);
} else {
setIcon(noIcon);
}
return this;
}
}
#SuppressWarnings("serial")
private class BooleanIconEditor extends AbstractCellEditor implements TableCellEditor, MouseListener {
private BooleanComponent boolComp;
private boolean isMouseEvent;
public BooleanIconEditor() {
boolComp = new BooleanComponent(false);
boolComp.addMouseListener(this);
InputMap im = boolComp.getInputMap(JComponent.WHEN_FOCUSED);
ActionMap am = boolComp.getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "click");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "click");
am.put("click", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Clicked");
boolComp.setValue(!boolComp.getValue());
}
});
}
#Override
public boolean isCellEditable(EventObject e) {
isMouseEvent = e instanceof MouseEvent;
return true; //(e instanceof MouseEvent);
}
#Override
public Object getCellEditorValue() {
return boolComp.getValue();
}
#Override
public Component getTableCellEditorComponent(JTable table,
Object value, boolean isSelected, int row, int column) {
boolean state = (boolean) value;
if (isMouseEvent) {
state = !state;
}
boolComp.setValue(state);
boolComp.setOpaque(isSelected);
boolComp.setBackground(table.getSelectionBackground());
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 JLabel {
private boolean value;
public BooleanComponent(boolean value) {
this.value = value;
}
public boolean getValue() {
return value;
}
public void setValue(boolean value) {
this.value = value;
if (value) {
setIcon(yesIcon);
} else{
setIcon(noIcon);
}
}
}
}
While #Mad's answer shows how to implement mouse handling and key bindings, an alternative is to use JToggleButton, the parent of JCheckBox, and a suitable Icon. The toggle button already knows how to display the chosen Icon and handle events. The modified ValueRenderer is shown below; the ValueEditor is unchanged; the text is optional.
private static class ValueRenderer extends JToggleButton
implements TableCellRenderer {
private static final Color hilite = new Color(0xE8E8E8);
private static final Icon YES = UIManager.getIcon("InternalFrame.maximizeIcon");
private static final Icon NO = UIManager.getIcon("InternalFrame.closeIcon");
public ValueRenderer() {
this.setOpaque(true);
this.setIcon(NO);
this.setSelectedIcon(YES);
}
...
}

Java Swing, JComboBox drop down list change listener before clicking

I have a JComboBox which has a list of midi files, I wonder if the following is doable :
when I click on the JComboBox, a drop down list opens up, when I move the mouse on to a midi file, it plays a 10 second sample sound, so I know what the file contains before I click and select that file, so if I have 50 midi files, I can open the list and move the mouse up and down the list without clicking on it, but still play the 10 second samples from the file the mouse points to, then after I decide which one, click on it, and that one will be the selected one in the JComboBox.
How to get notified for the mouse position change/pointing events from the JComboBox ?
How to get notified for the mouse position change/pointing events
from the JComboBox ?
by implements ItemListener, that fired twice (SELECTED and DESELECTED)
or
add there CustomRenderer
or ...
three possible Events for JComboBox for example
import java.awt.Component;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.plaf.basic.BasicComboBoxRenderer;
public class ComboBoxHoverOver {
private JComboBox combo = new JComboBox();
public ComboBoxHoverOver() {
combo.setPrototypeDisplayValue("XXXXXXXXXXXXXXXXXXXXXX");
combo.setRenderer(new ComboToolTipRenderer(combo));
combo.addItemListener(new ItemListener() {
#Override
public void itemStateChanged(ItemEvent e) {
System.out.println(combo.getSelectedItem().toString());
}
});
combo.addItem("");
combo.addItem("Long text 4");
combo.addItem("Long text 3");
combo.addItem("Long text 2");
combo.addItem("Long text 1");
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(combo);
f.pack();
f.setVisible(true);
}
private class ComboToolTipRenderer extends BasicComboBoxRenderer {
private static final long serialVersionUID = 1L;
private JComboBox combo;
private JList comboList;
ComboToolTipRenderer(JComboBox combo) {
this.combo = combo;
}
#Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (comboList == null) {
comboList = list;
KeyAdapter listener = new KeyAdapter() {
#Override
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_DOWN || e.getKeyCode() == KeyEvent.VK_UP) {
int x = 5;
int y = comboList.indexToLocation(comboList.getSelectedIndex()).y;
System.out.println(comboList.getSelectedIndex());
}
}
};
combo.addKeyListener(listener);
combo.getEditor().getEditorComponent().addKeyListener(listener);
}
if (isSelected) {
System.out.println(value.toString());
}
return this;
}
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
ComboBoxHoverOver comboBoxHoverOver = new ComboBoxHoverOver();
}
});
}
}
FocusListener might be a good idea. Here is some good information about how to implement one.
http://download.oracle.com/javase/tutorial/uiswing/events/focuslistener.html

Categories