A. The problem
The code below create a two column table that displays the name and department of employees. When the combo box with a list of department is clicked, it will spontaneously display a selected value other than the original value.
public class TableTest extends JFrame{
private JTable table;
private DefaultTableModel model;
private JComboBox<String> comboBox;
private DefaultComboBoxModel<String> deptData;
public TableTest(){
model = new MyTableModel();
model.addRow(new String[]{"John Smith", "Human Resource"});
model.addRow(new String[]{"Jane Shaw", "Human Resource"});
model.addRow(new String[]{"Dalene Young", "Sale"});
table = new JTable();
table.setRowHeight(table.getRowHeight()+10);
table.setModel(model);
table.getColumn("Department").setCellEditor(new MyCellEditor());
table.getColumn("Department").setCellRenderer(new MyCellRenderer());
deptData = new DefaultComboBoxModel<>();
deptData.addElement("Production");
deptData.addElement("Sale");
deptData.addElement("Customer Support");
deptData.addElement("Human Resource");
JScrollPane scrollPane = new JScrollPane(table);
add(scrollPane);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(300, 200);
setVisible(true);
}
public class MyTableModel extends DefaultTableModel {
String[] cols = {"Name", "Department"};
public MyTableModel() {
super();
setColumnIdentifiers(cols);
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return true;
}
@Override
public Class<? extends Object> getColumnClass(int columnIndex) {
if (columnIndex == 1){
return JComboBox.class;
}
return String.class;
}
}
private class MyCellEditor extends AbstractCellEditor implements TableCellEditor, ItemListener, PopupMenuListener{
private int theRow = 0;
private boolean served = false;
private String theDept = "";
@Override
public String getCellEditorValue() {
return theDept;
}
@Override
public Component getTableCellEditorComponent(JTable t, Object v, boolean seleted, int row, int col){
theRow = row;
theDept = (String)table.getValueAt(row, 1);
comboBox = new JComboBox<>(deptData);
comboBox.addPopupMenuListener(this);
comboBox.addItemListener(this);
comboBox.setSelectedItem(theDept);
served = false;
return comboBox;
}
@Override
public void itemStateChanged(ItemEvent e){
if(e.getStateChange() == ItemEvent.DESELECTED){
return;
}
if(served){
return;
}
JComboBox<String> combo = (JComboBox<String>) e.getSource();
String dep = (String) combo.getSelectedItem();
if (table.getCellEditor() != null)
table.getCellEditor().stopCellEditing();
combo.setSelectedItem(dep);
model.setValueAt(dep, theRow, 1);
served = true;
}
@Override
public void popupMenuCanceled(PopupMenuEvent e){
System.out.println("popup canceled...");
if (table.getCellEditor() != null)
table.getCellEditor().stopCellEditing();
}
@Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
System.out.println("popup to be invisible...");
if (table.getCellEditor() != null)
table.getCellEditor().stopCellEditing();
// model.fireTableDataChanged();
}
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
System.out.println("popup to be visible...");
}
}
private class MyCellRenderer extends DefaultTableCellRenderer {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
JComboBox<String> comp = new JComboBox<>(deptData);
String v = (String) table.getValueAt(row, 1);
comp.setSelectedItem(v);
return comp;
}
}
public static void main(String[] args){
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
new TableTest();
}
});
}
}
Once the code is executed, it looks like this.
Then you click on the Department combo box, it automatically select Sale before you select anything. And even worse, it does not allow you to select any value.
B. The solution
For the code to work properly:
1. Add a mouse listener to the JTable. In the mouseReleased() method, set the current selected value in the editing JComboBox to the corresponding current value in the table model, so that it will display the correct value when clicked.
2. The getCellEditorValue() in the Cell Editor class must return the newly selected value identified in the Item Listener, so that the cell renderer can use it to render the cell.
3. Each time the table data is changed, call the stopCellEditing() of the Cell Editor and fireTableDataChanged() of the Table Model to let the change be notified to all listeners.
4. Add an editingCombo field to determine when the ItemStateChanged() method to be executed.
The code below works properly. The modified codes are highlighted.
public class TableTest extends JFrame{
private JTable table;
private DefaultTableModel model;
private JComboBox<String> comboBox;
private DefaultComboBoxModel<String> deptData;
private boolean editingCombo = false;
public TableTest(){
deptData = new DefaultComboBoxModel<>();
deptData.addElement("Production");
deptData.addElement("Sale");
deptData.addElement("Customer Support");
deptData.addElement("Human Resource");
model = new MyTableModel();
model.addRow(new String[]{"John Smith", "Human Resource"});
model.addRow(new String[]{"Jane Shaw", "Human Resource"});
model.addRow(new String[]{"Dalene Young", "Sale"});
table = new JTable();
table.setRowHeight(table.getRowHeight()+10);
table.setModel(model);
table.getColumn("Department").setCellEditor(new MyCellEditor());
table.getColumn("Department").setCellRenderer(new MyCellRenderer());
table.addMouseListener(new MyMouseListener());
JScrollPane scrollPane = new JScrollPane(table);
add(scrollPane);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(300, 200);
setVisible(true);
}
public class MyTableModel extends DefaultTableModel {
String[] cols = {"Name", "Department"};
public MyTableModel() {
super();
setColumnIdentifiers(cols);
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return true;
}
@Override
public Class<? extends Object> getColumnClass(int columnIndex) {
if (columnIndex == 1){
return JComboBox.class;
}
return String.class;
}
}
private class MyCellEditor extends AbstractCellEditor implements TableCellEditor, ItemListener,PopupMenuListener{
private int theRow = 0;
private boolean served = false;
private String theDept = "";
@Override
public String getCellEditorValue() {
return theDept;
}
@Override
public Component getTableCellEditorComponent(JTable t, Object v, boolean seleted, int row, int col){
theRow = row;
theDept = (String)table.getValueAt(row, 1);
comboBox = new JComboBox<>(deptData);
comboBox.setSelectedItem(theDept);
comboBox.addPopupMenuListener(this);
comboBox.addItemListener(this);
served = false;
return comboBox;
}
@Override
public void itemStateChanged(ItemEvent e){
if(e.getStateChange() == ItemEvent.DESELECTED){
return;
}
if (!editingCombo){
return;
}
if(served){
return;
}
JComboBox<String> combo = (JComboBox<String>) e.getSource();
String dep = (String) combo.getSelectedItem();
theDept = dep;
System.out.println("ItemListener: selected="+dep+", row="+theRow);
table.setValueAt(dep, theRow, 1);
System.out.println(" ItemListener:"+table.getValueAt(theRow, 1));
if (table.getCellEditor() != null)
table.getCellEditor().stopCellEditing();
System.out.println(" ItemListener0:"+table.getValueAt(theRow, 1));
model.fireTableDataChanged();
System.out.println(" ItemListener1:"+table.getValueAt(theRow, 1));
served = true;
editingCombo = false;
System.out.println(" ItemListener2:"+table.getValueAt(theRow, 1));
}
@Override
public void popupMenuCanceled(PopupMenuEvent e){
System.out.println("popup canceled...");
if (table.getCellEditor() != null)
table.getCellEditor().stopCellEditing();
}
@Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
System.out.println("popup to be invisible..."+table.getValueAt(theRow, 1));
if (table.getCellEditor() != null)
table.getCellEditor().stopCellEditing();
model.fireTableDataChanged();
System.out.println(" popup to be invisible:"+table.getValueAt(theRow, 1));
editingCombo = false;
}
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
System.out.println("popup to be visible...");
}
}
private class MyCellRenderer extends DefaultTableCellRenderer {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
System.out.println ("Render: value="+value+":"+table.getValueAt(row, 1)+", row="+row);
JComboBox<String> comp = new JComboBox<>(deptData);
String v = (String) table.getValueAt(row, 1);
comp.setSelectedItem(v);
return comp;
}
}
private class MyMouseListener extends MouseAdapter {
@Override
public void mouseReleased(MouseEvent e) {
int row = table.rowAtPoint(e.getPoint());
int col = table.columnAtPoint(e.getPoint());
if (row < 0 || row >= table.getRowCount()
|| col < 0 || col >= table.getColumnCount()) {
return;
}
if (col == 1) {
System.out.println("mouse released: row=" + row + ", col=" + col);
comboBox.setSelectedItem(table.getValueAt(row, 1));
editingCombo = true;
}
}
}
public static void main(String[] args){
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
new TableTest();
}
});
}
}
---------------------------------------------------------------------------------------------------
If you have ever asked yourself these questions, this is the book for you. What is the meaning of life? Why do people suffer? What is in control of my life? Why is life the way it is? How can I stop suffering and be happy? How can I have a successful life? How can I have a life I like to have? How can I be the person I like to be? How can I be wiser and smarter? How can I have good and harmonious relations with others? Why do people meditate to achieve enlightenment? What is the true meaning of spiritual practice? Why all beings are one? Read the book free here.