I have a problem with the following code. My task is, I have to have radio buttons in the first column and when a user selects that radio button that row is selected and sent for processing. But my problem is, I am able to select the radio button which are in the first column, but afterwards when user clicks in any part of the table then my clicked radio button is being unchecked. I am not able to figure, why it is happeneing. I am really stuck with this problem. Help required. The following code shows my problem.
import java.awt.Component;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.ButtonGroup;
import javax.swing.DefaultCellEditor;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
public class DisplayTable extends JDialog {
public void initialize() {
SourceTableModel stm = new SourceTableModel();
JTable sourceTable = new JTable(stm);
sourceTable.getColumnModel().getColumn(0).setCellRenderer(new RadioButtonRenderer());
sourceTable.getColumnModel().getColumn(0).setCellEditor(new RadioButtonEditor(new JCheckBox ()));
JPanel panel = new JPanel();
panel.add(new JScrollPane(sourceTable));
add(panel);
setModal(true);
pack();
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new DisplayTable().initialize();
}
});
}
}
class SourceTableModel extends AbstractTableModel{
private static final long serialVersionUID = 1L;
private List<SourceModel> sourceList = new ArrayList<SourceModel>();
private String[] columnNamesList = {"Select", "Group", "Work"};
public SourceTableModel() {
this.sourceList = getSourceDOList();
}
public String getColumnName(int column) {
return columnNamesList[column];
}
public int getRowCount() {
return sourceList.size();
}
public int getColumnCount() {
return columnNamesList.length;
}
public Class<?> getColumnClass(int columnIndex) {
return (columnIndex == 0 ? Boolean.class : String.class);
}
public boolean isCellEditable(int rowIndex, int columnIndex) {
return (columnIndex == 0 ? true : false);
}
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
SourceModel model = (SourceModel) sourceList.get(rowIndex);
switch (columnIndex) {
case 0:
model.setSelect((Boolean)aValue);
break;
case 1:
model.setFactory((String) aValue);
break;
case 2:
model.setSupplier((String) aValue);
break;
}
fireTableCellUpdated(rowIndex, columnIndex);
}
public Object getValueAt(int rowIndex, int columnIndex) {
SourceModel source = sourceList.get(rowIndex);
switch(columnIndex){
case 0:
return source.isSelect();
case 1:
return source.getFactory();
case 2:
return source.getSupplier();
default:
return null;
}
}
private List<SourceModel> getSourceDOList() {
List<SourceModel> tempSourceList=new ArrayList<SourceModel>();
for (int index = 0; index < 5; index++) {
SourceModel source = new SourceModel();
source.setSelect(false);
source.setFactory("group");
source.setSupplier("Work");
tempSourceList.add(source);
}
return tempSourceList;
}
}
class SourceModel {
private boolean select;
private String factory;
private String supplier;
public SourceModel() {
// No Code;
}
public SourceModel(boolean select, String factory, String supplier) {
super();
this.select = select;
this.factory = factory;
this.supplier = supplier;
}
public boolean isSelect() {
return select;
}
public void setSelect(boolean select) {
this.select = select;
}
public String getFactory() {
return factory;
}
public void setFactory(String factory) {
this.factory = factory;
}
public String getSupplier() {
return supplier;
}
public void setSupplier(String supplier) {
this.supplier = supplier;
}
}
class RadioButtonEditor extends DefaultCellEditor implements ItemListener {
public JRadioButton btn = new JRadioButton();
public RadioButtonEditor(JCheckBox checkBox) {
super(checkBox);
}
public Component getTableCellEditorComponent(JTable table, Object
value, boolean isSelected, int row, int column) {
if (value==null)
return null;
btn.addItemListener(this);
if (( (Boolean) value).booleanValue())
btn.setSelected(true);
else
btn.setSelected(false);
return btn;
}
public Object getCellEditorValue() {
if(btn.isSelected() == true)
return new Boolean(true);
else
return new Boolean(false);
}
public void itemStateChanged(ItemEvent e) {
super.fireEditingStopped();
}
}
class RadioButtonRenderer implements TableCellRenderer {
public JRadioButton btn = new JRadioButton();
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
if (value==null) return null;
if(((Boolean)value).booleanValue())
btn.setSelected(true);
else
btn.setSelected(false);
if (isSelected) {
btn.setForeground(table.getSelectionForeground());
btn.setBackground(table.getSelectionBackground());
} else {
btn.setForeground(table.getForeground());
btn.setBackground(table.getBackground());
}
return btn;
}
}
EDIT:
I have updated my code and I have used Boolean class for the first column. The problem which I am facing is, if I remove super.fireEditingStopped(); from RadioButtonEditor class then I am able to check and then if I click at any part of the table then checked one I being unchecked. If I keep the super.fireEditingStopped(); then I am not even able to check the Radio button.
I know that super.fireEditingStopped(); will stop editing. But my question is how to check it?
P.S: Sorry I have posted my entire code. I thought it will be easy for some one to look at the problem.
This is the screen shot of the program.
From your illustration, it appears that you want to enforce mutual exclusion among the rows of a JTable, where each row has a single JRadioButton. As a ButtonGroup is unsuitable, this example due to #Guillaume Polet uses a custom manager.
I have a problem with the following code. My task is, I have to have
radio buttons in the first column and when a user selects that radio
button that row is selected and sent for processing. But my problem
is, I am able to select the radio button which are in the first
column, but afterwards when user clicks in any part of the table then
my clicked radio button is being unchecked. I am not able to figure,
why it is happeneing. I am really stuck with this problem. Help
required. The following code shows my problem.
don't to use JRadioButton, use built_in support for Boolean value in JTable == JCheckBox,
then you can sorting and filtering based on Boolean value
otherwise you have to override to String ("true" / "false")
there are a few good JRadioButtons as Renderer and Editor in JTable, including usage of JComboBox as Editor for RadioButtonGroup
If you need to dynamically change the Look and Feel, your CellEditor is recommended to extend Component.
//#see javax/swing/SwingUtilities.java
static void updateRendererOrEditorUI(Object rendererOrEditor) {
if (rendererOrEditor == null) {
return;
}
Component component = null;
if (rendererOrEditor instanceof Component) {
component = (Component)rendererOrEditor;
}
if (rendererOrEditor instanceof DefaultCellEditor) {
//Ahh, AbstractCellEditor ...
component = ((DefaultCellEditor)rendererOrEditor).getComponent();
}
if (component != null) {
SwingUtilities.updateComponentTreeUI(component);
}
}
Here is a "CellEditor extends JRadioButton ..." example:
import java.awt.*;
import java.awt.event.*;
import java.util.EventObject;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
public class DisplayTable2 extends JDialog {
public void initialize() {
Object[][] data = {
{ true, "Group1", "Work1" }, { false, "Group2", "Work2" },
{ false, "Group3", "Work3" }, { false, "Group4", "Work4" }
};
JTable sourceTable = new JTable(new SourceTableModel(data));
sourceTable.getColumnModel().getColumn(0).setCellRenderer(new RadioButtonRenderer());
sourceTable.getColumnModel().getColumn(0).setCellEditor(new RadioButtonEditor());
JPanel panel = new JPanel();
panel.add(new JScrollPane(sourceTable));
add(panel);
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
setModal(true);
pack();
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override public void run() {
new DisplayTable2().initialize();
}
});
}
}
class SourceTableModel extends DefaultTableModel {
private static final String[] columnNamesList = {"Select", "Group", "Work"};
public SourceTableModel(Object[][] data) {
super(data, columnNamesList);
}
#Override public Class<?> getColumnClass(int columnIndex) {
return (columnIndex == 0 ? Boolean.class : String.class);
}
#Override public boolean isCellEditable(int rowIndex, int columnIndex) {
return (columnIndex == 0 ? true : false);
}
#Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
if(columnIndex==0 && aValue instanceof Boolean) {
//lazy development
for(int i=0; i<getRowCount(); i++) {
super.setValueAt(i==rowIndex, i, columnIndex);
}
} else {
super.setValueAt(aValue, rowIndex, columnIndex);
}
}
}
class RadioButtonRenderer extends JRadioButton implements TableCellRenderer {
#Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if(value instanceof Boolean) {
setSelected((Boolean)value);
}
return this;
}
}
class RadioButtonEditor extends JRadioButton implements TableCellEditor {
public RadioButtonEditor() {
super();
addActionListener(new ActionListener() {
#Override public void actionPerformed(ActionEvent e) {
fireEditingStopped();
}
});
}
#Override public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
if(value instanceof Boolean) {
setSelected((Boolean)value);
}
return this;
}
#Override public Object getCellEditorValue() {
return isSelected();
}
//Copid from AbstractCellEditor
//protected EventListenerList listenerList = new EventListenerList();
//transient protected ChangeEvent changeEvent = null;
#Override public boolean isCellEditable(EventObject e) {
return true;
}
#Override public boolean shouldSelectCell(EventObject anEvent) {
return true;
}
#Override public boolean stopCellEditing() {
fireEditingStopped();
return true;
}
#Override public void cancelCellEditing() {
fireEditingCanceled();
}
#Override public void addCellEditorListener(CellEditorListener l) {
listenerList.add(CellEditorListener.class, l);
}
#Override public void removeCellEditorListener(CellEditorListener l) {
listenerList.remove(CellEditorListener.class, l);
}
public CellEditorListener[] getCellEditorListeners() {
return listenerList.getListeners(CellEditorListener.class);
}
protected void fireEditingStopped() {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for(int i = listeners.length-2; i>=0; i-=2) {
if(listeners[i]==CellEditorListener.class) {
// Lazily create the event:
if(changeEvent == null) changeEvent = new ChangeEvent(this);
((CellEditorListener)listeners[i+1]).editingStopped(changeEvent);
}
}
}
protected void fireEditingCanceled() {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for(int i = listeners.length-2; i>=0; i-=2) {
if(listeners[i]==CellEditorListener.class) {
// Lazily create the event:
if(changeEvent == null) changeEvent = new ChangeEvent(this);
((CellEditorListener)listeners[i+1]).editingCanceled(changeEvent);
}
}
}
}
Related
I have a JTable where one of the cell is a LocalDate which I can update by clicking on it and it opens a date picker. The problem is that the modification is not taken into account because the date picker event waits for an input from the user but does not block. And so the event that handles the edit ends before it receives the edit.
I've checked this issue : How to wait for an event to complete, before the program continue running in java and tried to block event child with CountDownLatch but it freezes the app. I'm not sure but I guess it is something about event blocking within another event that causes trouble.
Here's some code to test :
For editing the cell :
package com.swing.datepicker;
import com.github.lgooddatepicker.components.DatePicker;
import javax.swing.*;
import javax.swing.table.TableCellEditor;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.LocalDate;
public class DateOfBirthCellEditor extends AbstractCellEditor implements TableCellEditor, ActionListener {
private LocalDate curValue;
private final JButton button;
public DateOfBirthCellEditor() {
button = new JButton();
button.addActionListener(this);
button.setBorderPainted(false);
}
#Override
public void actionPerformed(ActionEvent e) {
DatePicker datePicker = new DatePicker();
datePicker.setDate(curValue);
button.add(datePicker);
datePicker.openPopup();
datePicker.addDateChangeListener(dateChangeEvent -> {
curValue = dateChangeEvent.getNewDate();
System.out.println("should happens before - curValue : " + curValue);
});
System.out.println("should happens after - curValue : " + curValue);
button.remove(datePicker);
fireEditingStopped();
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
curValue = (LocalDate) value;
return button;
}
#Override
public Object getCellEditorValue() {
return curValue;
}
}
main class :
package com.swing.datepicker;
import javax.swing.*;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;
import java.time.LocalDate;
import java.util.List;
public class TableTest extends JFrame {
public TableTest() throws Exception {
super("TEST");
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
initUI();
}
static class Dude {
String name;
LocalDate dateofbirth;
public Dude(String name, LocalDate dateofbirth) {
this.name = name;
this.dateofbirth = dateofbirth;
}
}
public void initUI() {
// Initial Settings
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(600, 400);
this.setLocationRelativeTo(null);
JPanel contentPane = (JPanel) this.getContentPane();
TableModel tableModel = new TableModel() {
final List<Dude> data = List.of(new Dude("John", LocalDate.of(1998, 6,30)));
final String[] headers = {"name", "date of birth"};
#Override
public int getRowCount() {
return data.size();
}
#Override
public int getColumnCount() {
return headers.length;
}
#Override
public String getColumnName(int columnIndex) {
return headers[columnIndex];
}
#Override
public Class<?> getColumnClass(int columnIndex) {
if (columnIndex == 1) {
return LocalDate.class;
}
return Object.class;
}
#Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return true;
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
return switch (columnIndex) {
case 0 -> data.get(rowIndex).name;
case 1 -> data.get(rowIndex).dateofbirth;
default -> null;
};
}
#Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
if (aValue != null) {
switch (columnIndex) {
case 0 -> data.get(rowIndex).name = (String) aValue;
case 1 -> data.get(rowIndex).dateofbirth = (LocalDate) aValue;
}
}
}
#Override
public void addTableModelListener(TableModelListener l) {}
#Override
public void removeTableModelListener(TableModelListener l) {}
};
JTable table = new JTable(tableModel);
table.setDefaultEditor(LocalDate.class, new DateOfBirthCellEditor());
JPanel tablePanel = new JPanel();
tablePanel.add(table);
contentPane.add(tablePanel);
}
public static void main(String[] args) throws Exception {
TableTest window = new TableTest();
window.setVisible(true);
}
}
I used that date picker in other parts of my code, that's why I wanna keep it and do some weird code in editing the cell. I have this in my pom :
<dependency>
<groupId>com.github.lgooddatepicker</groupId>
<artifactId>LGoodDatePicker</artifactId>
<version>11.2.1</version>
</dependency>
Thanks in advance.
I need to provide some disabled items in a combobox. All works fine except preventing of combobox from closing after click on a disabled item.
Here is my code:
import java.awt.Component;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import javax.swing.plaf.basic.BasicComboBoxRenderer;
import javax.swing.plaf.basic.ComboPopup;
public class DisabledCombo {
public static void main(String[] args) {
final DisabledSupportComboModel model = new DisabledSupportComboModel();
model.addElement(new Item("First element"));
model.addElement(new Item("Second element"));
model.addElement(new Item("Disabled", false));
model.addElement(new Item("Fourth element"));
final JComboBox<Item> itemCombo = new JComboBox<DisabledCombo.Item>(model);
itemCombo.setRenderer(new DisabledSupportComboRenderer());
final ComboPopup popup = (ComboPopup) itemCombo.getUI().getAccessibleChild(itemCombo, 0);
final JList<?> l = popup.getList();
final MouseListener[] listeners = l.getMouseListeners();
for (final MouseListener ml : listeners) {
l.removeMouseListener(ml);
System.out.println("remove listener: " + ml);
}
System.out.println("Number of listeners: " + l.getMouseListeners().length);
l.addMouseListener(new MouseAdapter() {
#Override
public void mouseReleased(MouseEvent e) {
System.out.println("Release");
final int idx = l.locationToIndex(e.getPoint());
if (idx >= 0 && l.getModel().getElementAt(idx) instanceof Item) {
final Item itm = (Item) l.getModel().getElementAt(idx);
if (!itm.isEnabled()) {
e.consume();
}
}
}
#Override
public void mouseClicked(MouseEvent e) {
System.out.println("Click");
final int idx = l.locationToIndex(e.getPoint());
if (idx >= 0 && l.getModel().getElementAt(idx) instanceof Item) {
final Item itm = (Item) l.getModel().getElementAt(idx);
if (!itm.isEnabled()) {
e.consume();
}
}
}
});
for (final MouseListener ml : listeners) {
l.addMouseListener(ml);
}
final JFrame frm = new JFrame("Combo test");
frm.add(itemCombo);
frm.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frm.pack();
frm.setVisible(true);
}
private static class Item {
private final Object value;
private final boolean enabled;
public Item(Object aValue) {
value = aValue;
enabled = true;
}
public Item(Object aValue, boolean isEnabled) {
value = aValue;
enabled = isEnabled;
}
public Object getValue() {
return value;
}
public boolean isEnabled() {
return enabled;
}
/**
* {#inheritDoc}
*/
#Override
public String toString() {
return null == value? null : value.toString();
}
}
private static class DisabledSupportComboModel extends DefaultComboBoxModel<Item> {
/**
* {#inheritDoc}
*/
#Override
public void setSelectedItem(Object anObject) {
if (anObject instanceof Item) {
if (((Item) anObject).isEnabled()) {
super.setSelectedItem(anObject);
}
} else {
super.setSelectedItem(anObject);
}
}
}
private static class DisabledSupportComboRenderer extends BasicComboBoxRenderer {
/**
* {#inheritDoc}
*/
#Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (value instanceof Item) {
if (((Item) value).isEnabled()) {
setForeground(isSelected? list.getSelectionForeground() : list.getForeground());
setBackground(isSelected? list.getSelectionBackground() : list.getBackground());
} else {
setForeground(UIManager.getColor("Label.disabledForeground"));
setBackground(list.getBackground());
}
} else {
setForeground(isSelected? list.getSelectionForeground() : list.getForeground());
setBackground(isSelected? list.getSelectionBackground() : list.getBackground());
}
return this;
}
}
}
My problem is, that I get mouseReleased event, but no mouseClicked event. The only way to get mouseClicked event is to register AWTEventListener for mouse events using the Toolkit class. But it's realy ugly here. The approach to show the popup again using the setPopupVisible(true) is also difficult here due to eventually scroll pane in popup (the real combobox can have about 30 entries, so I need to save the scroll bar value to restore the drop down list at the same position). Can somebody advise me, how can I prevent the combo popup from closing?
Here's my attempt:
Override JComboBox#setPopupVisible(boolean) instead of using JList#addMouseListener(...)
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class DisabledCombo2 {
public static JComponent makeUI() {
DisabledSupportComboModel model = new DisabledSupportComboModel();
model.addElement(new Item("First element"));
model.addElement(new Item("Second element"));
model.addElement(new Item("Disabled", false));
model.addElement(new Item("Fourth element"));
JComboBox<Item> itemCombo = new JComboBox<Item>(model) {
//#see http://java-swing-tips.blogspot.jp/2010/03/non-selectable-jcombobox-items.html
private boolean isDisableIndex;
#Override public void setPopupVisible(boolean v) {
if (!v && isDisableIndex) {
//Do nothing(prevent the combo popup from closing)
isDisableIndex = false;
} else {
super.setPopupVisible(v);
}
}
#Override public void setSelectedObject(Object o) {
if (o instanceof Item && !((Item) o).isEnabled()) {
isDisableIndex = true;
} else {
super.setSelectedObject(o);
}
}
#Override public void setSelectedIndex(int index) {
Object o = getItemAt(index);
if (o instanceof Item && !((Item) o).isEnabled()) {
isDisableIndex = true;
} else {
super.setSelectedIndex(index);
}
}
};
itemCombo.setRenderer(new DisabledSupportComboRenderer());
return itemCombo;
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI() {
JFrame f = new JFrame("Combo test2");
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.getContentPane().add(makeUI());
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class Item {
private final Object value;
private final boolean enabled;
public Item(Object aValue) {
value = aValue;
enabled = true;
}
public Item(Object aValue, boolean isEnabled) {
value = aValue;
enabled = isEnabled;
}
public Object getValue() {
return value;
}
public boolean isEnabled() {
return enabled;
}
#Override public String toString() {
return null == value ? null : value.toString();
}
}
class DisabledSupportComboModel extends DefaultComboBoxModel<Item> {
#Override public void setSelectedItem(Object anObject) {
if (anObject instanceof Item) {
if (((Item) anObject).isEnabled()) {
super.setSelectedItem(anObject);
}
} else {
super.setSelectedItem(anObject);
}
}
}
class DisabledSupportComboRenderer extends DefaultListCellRenderer {
#Override public Component getListCellRendererComponent(
JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (value instanceof Item) {
if (((Item) value).isEnabled()) {
setForeground(isSelected ? list.getSelectionForeground() : list.getForeground());
setBackground(isSelected ? list.getSelectionBackground() : list.getBackground());
} else {
setForeground(UIManager.getColor("Label.disabledForeground"));
setBackground(list.getBackground());
}
} else {
setForeground(isSelected ? list.getSelectionForeground() : list.getForeground());
setBackground(isSelected ? list.getSelectionBackground() : list.getBackground());
}
return this;
}
}
I found some simple solution to always keep popup open, until user clicks outside the popup. It may be useful with some custom JComboBox'es, like the one I have in my project, but is a little hacky.
public class MyComboBox extends JComboBox
{
boolean select_action_performed = false; //check when user select item
public MyComboBox(){
setRenderer(new MyComboBoxRenderer()); //our spesial render
addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
//Do stuff when user select item
select_action_performed = true; //set the flag
}
});
}
class MyComboBoxRenderer extends BasicComboBoxRenderer {
public Component getListCellRendererComponent(JList list, Object value,
int index, boolean isSelected, boolean cellHasFocus) {
if (index == -1){ //if popup hidden
if (select_action_performed) {
showPopup(); //show it again
select_action_performed = false; //and remove the flag
}
return r;
}
}
}
}
In my main application, the JTable is losing focus when a dialog is shown from a cell editor component.
Below is a simple SSCCE I made for you to see the problem.
Do these simples experiments:
Press F2 in the first table column to start editing. Then change to column contents to the number 2 and press ENTER key. The table will lose focus and the first field in the form with get focus.
Press F2 in the first table column to start editing. Then change to column contents to the number 2 and press TAB key. The table will lose focus and the first field in the form with get focus.
The first field in the form is also a SearchField component. Because it is not in the JTable, it behaves properly when you change its contente to the number 2 and commit the edit (with ENTER or TAB).
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Objects;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultCellEditor;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import javax.swing.text.DefaultFormatterFactory;
import javax.swing.text.NumberFormatter;
public class SSCCE extends JPanel
{
private SSCCE()
{
setLayout(new BorderLayout());
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
JPanel pnlFields = new JPanel();
pnlFields.setLayout(new BoxLayout(pnlFields, BoxLayout.PAGE_AXIS));
pnlFields.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
SearchField field1 = new SearchField();
configureField(field1);
pnlFields.add(field1);
pnlFields.add(Box.createRigidArea(new Dimension(0, 3)));
JTextField field2 = new JTextField();
configureField(field2);
pnlFields.add(field2);
add(pnlFields, BorderLayout.PAGE_START);
add(new JScrollPane(createTable()), BorderLayout.CENTER);
}
private void configureField(JTextField field)
{
field.setPreferredSize(new Dimension(150, field.getPreferredSize().height));
field.setMaximumSize(field.getPreferredSize());
field.setAlignmentX(LEFT_ALIGNMENT);
}
private JTable createTable()
{
JTable table = new JTable(new CustomTableModel());
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.setCellSelectionEnabled(true);
table.getTableHeader().setReorderingAllowed(false);
table.setPreferredScrollableViewportSize(new Dimension(500, 170));
table.setDefaultEditor(Integer.class, new SearchFieldCellEditor(new SearchField()));
return table;
}
private static void createAndShowGUI()
{
JFrame frame = new JFrame("SSCCE (JTable Loses Focus)");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new SSCCE());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(
new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
}
);
}
}
class CustomTableModel extends AbstractTableModel
{
private String[] columnNames = {"Column1 (Search Field)", "Column 2"};
private Class<?>[] columnTypes = {Integer.class, String.class};
private Object[][] data = {{1, ""}, {3, ""}, {4, ""}, {5, ""}, {6, ""}};
#Override
public int getColumnCount()
{
return columnNames.length;
}
#Override
public int getRowCount()
{
return data.length;
}
#Override
public String getColumnName(int col)
{
return columnNames[col];
}
#Override
public Object getValueAt(int row, int col)
{
return data[row][col];
}
#Override
public Class<?> getColumnClass(int c)
{
return columnTypes[c];
}
#Override
public boolean isCellEditable(int rowIndex, int columnIndex)
{
return true;
}
#Override
public void setValueAt(Object value, int row, int col)
{
data[row][col] = value;
fireTableCellUpdated(row, col);
}
}
class SearchFieldCellEditor extends DefaultCellEditor
{
SearchFieldCellEditor(final SearchField searchField)
{
super(searchField);
searchField.removeActionListener(delegate);
delegate = new EditorDelegate()
{
#Override
public void setValue(Object value)
{
searchField.setValue(value);
}
#Override
public Object getCellEditorValue()
{
return searchField.getValue();
}
};
searchField.addActionListener(delegate);
}
#Override
public boolean stopCellEditing()
{
try
{
((SearchField) getComponent()).commitEdit();
}
catch (ParseException ex)
{
ex.printStackTrace();
}
return super.stopCellEditing();
}
}
class SearchField extends JFormattedTextField implements PropertyChangeListener
{
private Object _oldValue;
SearchField()
{
setupFormatter();
addPropertyChangeListener("value", this);
}
private void setupFormatter()
{
NumberFormat integerFormat = NumberFormat.getIntegerInstance();
integerFormat.setGroupingUsed(false);
NumberFormatter integerFormatter =
new NumberFormatter(integerFormat)
{
#Override
public Object stringToValue(String text) throws ParseException
{
return text.isEmpty() ? null : super.stringToValue(text);
}
};
integerFormatter.setValueClass(Integer.class);
integerFormatter.setMinimum(Integer.MIN_VALUE);
integerFormatter.setMaximum(Integer.MAX_VALUE);
setFormatterFactory(new DefaultFormatterFactory(integerFormatter));
}
#Override
public void propertyChange(PropertyChangeEvent evt)
{
Object newValue = evt.getNewValue();
if (!Objects.equals(newValue, _oldValue))
{
_oldValue = newValue;
// Suppose that a value of 2 means that the data wasn't found.
// So we display a message to the user.
if (new Integer(2).equals(newValue))
{
JOptionPane.showMessageDialog(
null, "Not found: " + newValue + ".", "Warning",
JOptionPane.WARNING_MESSAGE);
}
}
}
}
So, is there a way to solve this problem? The solution of this issue is very important to me.
Thank you.
Marcos
* UPDATE *
I think I've found a solution, but I would like to have your opinion if it is really a trustworthy solution.
Change the stopCellEditing method to this and test the SSCCE again:
#Override
public boolean stopCellEditing()
{
SearchField searchField = (SearchField) getComponent();
try
{
searchField.commitEdit();
}
catch (ParseException ex)
{
ex.printStackTrace();
}
Component table = searchField.getParent();
table.requestFocusInWindow();
return super.stopCellEditing();
}
So, do you think this really solves the problem or is there any flaw?
Marcos
UPDATE 2
I've found a little flaw. It is corrected with these changes:
class SearchFieldCellEditor extends DefaultCellEditor
{
SearchFieldCellEditor(final SearchField searchField)
{
super(searchField);
searchField.removeActionListener(delegate);
delegate = new EditorDelegate()
{
#Override
public void setValue(Object value)
{
searchField.setValue(value);
}
#Override
public Object getCellEditorValue()
{
return searchField.getValue();
}
};
searchField.addActionListener(delegate);
}
#Override
public Component getTableCellEditorComponent(
JTable table, Object value, boolean isSelected, int row, int column)
{
SearchField searchField = (SearchField) getComponent();
searchField.setPreparingForEdit(true);
try
{
return super.getTableCellEditorComponent(
table, value, isSelected, row, column);
}
finally
{
searchField.setPreparingForEdit(false);
}
}
#Override
public boolean stopCellEditing()
{
SearchField searchField = (SearchField) getComponent();
try
{
searchField.commitEdit();
}
catch (ParseException ex)
{
ex.printStackTrace();
}
Component table = searchField.getParent();
table.requestFocusInWindow();
return super.stopCellEditing();
}
}
class SearchField extends JFormattedTextField implements PropertyChangeListener
{
private boolean _isPreparingForEdit;
private Object _oldValue;
SearchField()
{
setupFormatter();
addPropertyChangeListener("value", this);
}
void setPreparingForEdit(boolean isPreparingForEdit)
{
_isPreparingForEdit = isPreparingForEdit;
}
private void setupFormatter()
{
NumberFormat integerFormat = NumberFormat.getIntegerInstance();
integerFormat.setGroupingUsed(false);
NumberFormatter integerFormatter =
new NumberFormatter(integerFormat)
{
#Override
public Object stringToValue(String text) throws ParseException
{
return text.isEmpty() ? null : super.stringToValue(text);
}
};
integerFormatter.setValueClass(Integer.class);
integerFormatter.setMinimum(Integer.MIN_VALUE);
integerFormatter.setMaximum(Integer.MAX_VALUE);
setFormatterFactory(new DefaultFormatterFactory(integerFormatter));
}
#Override
public void propertyChange(PropertyChangeEvent evt)
{
final Object newValue = evt.getNewValue();
if (!Objects.equals(newValue, _oldValue))
{
_oldValue = newValue;
// Suppose that a value of 2 means that the data wasn't found.
// So we display a message to the user.
if (new Integer(2).equals(newValue) && !_isPreparingForEdit)
{
JOptionPane.showMessageDialog(null, "Not found: " + newValue + ".", "Warning",
JOptionPane.WARNING_MESSAGE);
}
}
}
}
Have you found any more flaws too? I would like to have your review.
Marcos
UPDATE 3
Another solution after suggestion by kleopatra :
class SearchFieldCellEditor extends DefaultCellEditor
{
SearchFieldCellEditor(final SearchField searchField)
{
super(searchField);
searchField.setShowMessageAsynchronously(true);
searchField.removeActionListener(delegate);
delegate = new EditorDelegate()
{
#Override
public void setValue(Object value)
{
searchField.setValue(value);
}
#Override
public Object getCellEditorValue()
{
return searchField.getValue();
}
};
searchField.addActionListener(delegate);
}
#Override
public Component getTableCellEditorComponent(
JTable table, Object value, boolean isSelected, int row, int column)
{
SearchField searchField = (SearchField) getComponent();
searchField.setPreparingForEdit(true);
try
{
return super.getTableCellEditorComponent(
table, value, isSelected, row, column);
}
finally
{
searchField.setPreparingForEdit(false);
}
}
#Override
public boolean stopCellEditing()
{
SearchField searchField = (SearchField) getComponent();
try
{
searchField.commitEdit();
}
catch (ParseException ex)
{
ex.printStackTrace();
}
return super.stopCellEditing();
}
}
class SearchField extends JFormattedTextField implements PropertyChangeListener
{
private boolean _showMessageAsynchronously;
private boolean _isPreparingForEdit;
private Object _oldValue;
SearchField()
{
setupFormatter();
addPropertyChangeListener("value", this);
}
public boolean isShowMessageAsynchronously()
{
return _showMessageAsynchronously;
}
public void setShowMessageAsynchronously(boolean showMessageAsynchronously)
{
_showMessageAsynchronously = showMessageAsynchronously;
}
void setPreparingForEdit(boolean isPreparingForEdit)
{
_isPreparingForEdit = isPreparingForEdit;
}
private void setupFormatter()
{
NumberFormat integerFormat = NumberFormat.getIntegerInstance();
integerFormat.setGroupingUsed(false);
NumberFormatter integerFormatter =
new NumberFormatter(integerFormat)
{
#Override
public Object stringToValue(String text) throws ParseException
{
return text.isEmpty() ? null : super.stringToValue(text);
}
};
integerFormatter.setValueClass(Integer.class);
integerFormatter.setMinimum(Integer.MIN_VALUE);
integerFormatter.setMaximum(Integer.MAX_VALUE);
setFormatterFactory(new DefaultFormatterFactory(integerFormatter));
}
#Override
public void propertyChange(PropertyChangeEvent evt)
{
final Object newValue = evt.getNewValue();
if (!Objects.equals(newValue, _oldValue))
{
_oldValue = newValue;
// Suppose that a value of 2 means that the data wasn't found.
// So we display a message to the user.
if (new Integer(2).equals(newValue) && !_isPreparingForEdit)
{
if (_showMessageAsynchronously)
{
SwingUtilities.invokeLater(
new Runnable()
{
#Override
public void run()
{
showMessage(newValue);
}
}
);
}
else
{
showMessage(newValue);
}
}
}
}
private void showMessage(Object value)
{
JOptionPane.showMessageDialog(null, "Not found: " + value + ".",
"Warning", JOptionPane.WARNING_MESSAGE);
}
}
Comments and suggestions about this last solution are still appreciated. Is this the ultimate and optimal solution?
Marcos
As I already commented: it's a bit fishy to change the state of the table in the editor, especially if it's related to focus which is brittle even at its best. So I would go to great lengths to avoid it.
The mis-behaviour feels similar to an incorrectly implemented InputVerifier which has side-effects (like grabbing the focus) in its verify vs. in its shouldYieldFocus as would be correct: in such a context the focusManager gets confused, it "forgets" about the natural last-focusOwner-before.
The remedy might be to let the manager do its job first, and show the message only when it's done. In your example code that can be achieved by wrapping into an invokeLater:
if (needsMessage()) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JOptionPane.showMessageDialog(null, "Not found: " +
newValue + ".", "Warning",
JOptionPane.WARNING_MESSAGE);
}
});
}
Do the editing in the stopCellEditing() method.
In this example you are forced to enter a string of 5 characters:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.event.*;
import javax.swing.border.*;
import javax.swing.table.*;
public class TableEdit extends JFrame
{
TableEdit()
{
JTable table = new JTable(5,5);
table.setPreferredScrollableViewportSize(table.getPreferredSize());
JScrollPane scrollpane = new JScrollPane(table);
add(scrollpane);
// Use a custom editor
TableCellEditor fce = new FiveCharacterEditor();
table.setDefaultEditor(Object.class, fce);
add(new JTextField(), BorderLayout.NORTH);
}
class FiveCharacterEditor extends DefaultCellEditor
{
FiveCharacterEditor()
{
super( new JTextField() );
}
public boolean stopCellEditing()
{
JTable table = (JTable)getComponent().getParent();
try
{
System.out.println(getCellEditorValue().getClass());
String editingValue = (String)getCellEditorValue();
if(editingValue.length() != 5)
{
JTextField textField = (JTextField)getComponent();
textField.setBorder(new LineBorder(Color.red));
textField.selectAll();
textField.requestFocusInWindow();
JOptionPane.showMessageDialog(
null,
"Please enter string with 5 letters.",
"Alert!",JOptionPane.ERROR_MESSAGE);
return false;
}
}
catch(ClassCastException exception)
{
return false;
}
return super.stopCellEditing();
}
public Component getTableCellEditorComponent(
JTable table, Object value, boolean isSelected, int row, int column)
{
Component c = super.getTableCellEditorComponent(
table, value, isSelected, row, column);
((JComponent)c).setBorder(new LineBorder(Color.black));
return c;
}
}
public static void main(String [] args)
{
JFrame frame = new TableEdit();
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible(true);
}
}
I have here a JTable with two(2) columns. The right column is an editable one while the other is not.
So, what my problem is that whenever the user changed the value of a cell, that specific cell will changed its cell color.
I wanna do this because I want to let the user know that he/she made some changes in the table.
I found this somewhere and it somehow solved my problem but 1 thing that didn't come up with my expectation is that after changing the value and clicked another cell, the color changes back to its original color. I want to let it stay until it is saved.
#Override
public Component prepareEditor(TableCellEditor editor, int data, int columns) {
Component c = super.prepareEditor(editor, data, columns);
c.setBackground(Color.RED);
return c;
}
Is it possible? If yes, please show some example.
UPDATE:
String[] columnname = {"Student Name", "Grade"};
Object[][] data = {};
gradetable = new JTable(data, columnname){
private Object[][] rowData;
public boolean isCellEditable(int data, int columns){
return columns == 1;
}
public Component prepareRenderer(TableCellRenderer r, int data, int columns){
final Component c = super.prepareRenderer(r, data, columns);
if (data % 2 == 0){
c.setBackground(Color.LIGHT_GRAY);
}
else{
c.setBackground(Color.WHITE);
}
if (isCellSelected(data, columns)){
c.setBackground(Color.ORANGE);
}
return c;
}
#Override
public Component prepareEditor(TableCellEditor editor, int data, int columns) {
Component c = super.prepareEditor(editor, data, columns);
c.setBackground(Color.RED);
return c;
}
};
gradetable.setModel(new DefaultTableModel(data, columnname));
gradetable.setPreferredScrollableViewportSize(new Dimension (350, 130));
gradetable.setFillsViewportHeight(true);
gradetable.getTableHeader().setReorderingAllowed(false);
gradetable.setGridColor(new Color(128,128,128,128));
JScrollPane jsp = new JScrollPane(gradetable);
panel3.add(jsp);
Tables use a TableCellRenderer to paint values on the screen. The editors and renderers don't actually have anything to do with each other (from a painting point of view).
So once the editor has been dismissed (accepted or cancelled), the cell is repainted using the assigned TableCellRenderer
You need to supply, in your table model, some way to determine which rows have been updated and change the state of the renderer to match.
FYI- The DefaultTableCellRenderer uses a JLabel as it's base renderer, so it is transparent by default; you will need to make it opaque to make it render properly.
Check out Using custom renderers for more details
Update with example
This is nothing more then a proof of concept. It will not meet your absolute requirements and you should take a serious look at the tutorial linked above.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
public class TableEdit {
public static void main(String[] args) {
new TableEdit();
}
public TableEdit() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new BorderLayout());
JTable table = new JTable(new MyTableModel());
table.setSurrendersFocusOnKeystroke(true);
TableColumnModel model = table.getColumnModel();
model.getColumn(1).setCellRenderer(new MyTableCellRenderer());
add(new JScrollPane(table));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
public class MyData {
private String key;
private String value;
private boolean changed;
public MyData(String key, String value) {
this.key = key;
this.value = value;
this.changed = false;
}
public String getKey() {
return key;
}
public String getValue() {
return value;
}
public void setValue(String newValue) {
if (value == null ? newValue != null : !value.equals(newValue)) {
value = newValue;
changed = true;
}
}
public boolean hasChanged() {
return changed;
}
}
public class MyTableModel extends AbstractTableModel {
private List<MyData> data;
public MyTableModel() {
data = new ArrayList<>(25);
for (int index = 0; index < 5; index++) {
data.add(new MyData("A" + (index + 1), "B" + (index + 1)));
}
}
#Override
public int getRowCount() {
return data.size();
}
#Override
public int getColumnCount() {
return 2;
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
MyData myData = data.get(rowIndex);
Object value = null;
switch (columnIndex) {
case 0:
value = myData.getKey();
break;
case 1:
value = myData.getValue();
break;
}
return value;
}
#Override
public Class<?> getColumnClass(int columnIndex) {
return String.class;
}
#Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return columnIndex == 1;
}
public boolean hasChanged(int rowIndex) {
MyData myData = data.get(rowIndex);
return myData.hasChanged();
}
#Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
MyData myData = data.get(rowIndex);
myData.setValue(aValue == null ? null : aValue.toString());
}
}
public class MyTableCellRenderer extends DefaultTableCellRenderer {
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
setOpaque(isSelected);
TableModel model = table.getModel();
if (model instanceof MyTableModel) {
MyTableModel myModel = (MyTableModel) model;
if (myModel.hasChanged(row)) {
if (!isSelected) {
setBackground(Color.RED);
setOpaque(true);
}
}
}
return this;
}
}
}
I have a JTable with a custom TableCellRenderer and a custom TableCellEditor. By default, the first click on a table row switch from renderer to editor and the second click select the row.
Is there any way I can make the row selected on a single click (and swith to the editor)?
I have tried to use:
table.getSelectionModel().setSelectionInterval(row, row);
in my getTableCellEditorComponent but it doesn't work, and if I add it to my getTableCellRendererComponent it works, but only sometimes.
Here is a full example:
public class SelectRowDemo extends JFrame {
public SelectRowDemo() {
CellRendererAndEditor rendererAndEditor = new CellRendererAndEditor();
StringTableModel model = new StringTableModel();
JTable table = new JTable(model);
table.setDefaultEditor(String.class, rendererAndEditor);
table.setDefaultRenderer(String.class, rendererAndEditor);
model.addElement("");
model.addElement("");
model.addElement("");
add(new JScrollPane(table));
pack();
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");
} catch (Exception e) {
e.printStackTrace();
}
new SelectRowDemo();
}
});
}
class CellRendererAndEditor extends AbstractCellEditor implements TableCellEditor, TableCellRenderer {
private final JLabel renderer = new JLabel();
private final JLabel editor = new JLabel();
#Override
public Object getCellEditorValue() {
return editor.getText();
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
String str = "renderer ";
str += (isSelected) ? "selected" : "not selected";
renderer.setText(str);
return renderer;
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int row, int column) {
table.getSelectionModel().setSelectionInterval(row, row);
String str = "editor ";
str += (isSelected) ? "selected" : "not selected";
editor.setText(str);
return editor;
}
}
class StringTableModel extends AbstractTableModel {
private final List<String> data = new ArrayList<String>();
#Override
public int getColumnCount() {
return 1;
}
#Override
public int getRowCount() {
return data.size();
}
#Override
public Object getValueAt(int row, int column) {
return data.get(row);
}
#Override
public Class<?> getColumnClass(int column) {
return String.class;
}
#Override
public boolean isCellEditable(int row, int column) {
return true;
}
#Override
public void setValueAt(Object aValue, int row, int column) {
if(aValue instanceof String) {
data.set(row, (String)aValue);
fireTableRowsUpdated(row, column);
} else throw new IllegalStateException("aValue is not a String");
}
public void addElement(String s) {
data.add(s);
}
}
}
What's happening is that the table UI is starting to edit the cell before changing selection. If you put a println in getTableCellEditor() and shouldSelectCell(), the editor gets called first, with isSelected == false, then it calls shouldSelectCell and changes the selection.
You can see exactly where it happens in BasicTableUI.adjustSelection(MouseEvent).
boolean dragEnabled = table.getDragEnabled();
if (!dragEnabled && !isFileList && table.editCellAt(pressedRow, pressedCol, e)) {
setDispatchComponent(e);
repostEvent(e);
}
CellEditor editor = table.getCellEditor();
if (dragEnabled || editor == null || editor.shouldSelectCell(e)) {
table.changeSelection(pressedRow, pressedCol,
BasicGraphicsUtils.isMenuShortcutKeyDown(e),
e.isShiftDown());
}
As for rendering purposes, I'd just render it as if selected == true, since it will before that event is finished processing.