I have a custom JTable with a custom TableModel using a JComboBox as a cell editor. The ComboBox also has a custom ComboBoxModel
The ComboBox model holds multiple fields that will be used to update the data behind the JTable and afterwards update a database.
The following is a simple example to show the problem I am encountering. Steps to reproduce:
- Click on an cell
- Select a value from the ComboBox drop-down list
- Click on a different cell
- Click back on the first selected cell
The second cell will get the value from the first one.
Why is this happening? Why does the ComboBox model change before stopCellEditing exists?
import javax.swing.DefaultCellEditor;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
public class TestComboCellEditor {
public static void main(String[] args) {
TestComboCellEditor test = new TestComboCellEditor();
test.go();
}
public void go() {
//create the frame
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// create and add a tabbed pane to the frame
JTabbedPane tabbedPane = new JTabbedPane();
frame.getContentPane().add(tabbedPane);
//create a table and add it to a scroll pane in a new tab
final JTable table = new JTable(new DefaultTableModel(new Object[]{"A", "B"}, 5));
JScrollPane scrollPane = new JScrollPane(table);
tabbedPane.addTab("test", scrollPane);
// create a simple JComboBox and set is as table cell editor on column A
Object[] comboElements = {"aaaaa1", "aaaaaa2", "b"};
final JComboBox comboBox = new JComboBox(comboElements);
comboBox.setEditable(true);
table.getColumn("A").setCellEditor(new DefaultCellEditor(comboBox) {
@Override
public boolean stopCellEditing() {
if (comboBox.isEditable()) {
DefaultComboBoxModel comboModel = (DefaultComboBoxModel) comboBox.getModel();
String selectedItem = (String) comboModel.getSelectedItem();
int selectedIndex = comboModel.getIndexOf(selectedItem);
if (!(selectedIndex == -1)) {
// the selected item exists as an Option inside the ComboBox
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
int selectedRow = table.getSelectedRow();
int selectedColumn = table.getSelectedColumn();
tableModel.setValueAt(selectedItem, selectedRow, selectedColumn);
} else if (selectedItem != null) {
// missing code - adding new info to a custom JComboBox model and to alter info inside a custom table model
}
}
return super.stopCellEditing();
}
});
// pack and show frame
frame.pack();
frame.setVisible(true);
}
}
Here is an approach that keeps all the code in the editor:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.table.*;
public class TestComboCellEditor {
public static void main(String[] args) {
TestComboCellEditor test = new TestComboCellEditor();
test.go();
}
public void go() {
//create the frame
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// create and add a tabbed pane to the frame
JTabbedPane tabbedPane = new JTabbedPane();
frame.getContentPane().add(tabbedPane);
//create a table and add it to a scroll pane in a new tab
final JTable table = new JTable(new DefaultTableModel(new Object[]{"A", "B"}, 5));
JScrollPane scrollPane = new JScrollPane(table);
tabbedPane.addTab("test", scrollPane);
// create a simple JComboBox and set is as table cell editor on column A
Object[] comboElements = {"aaaaa1", "aaaaaa2", "b"};
final JComboBox comboBox = new JComboBox(comboElements);
comboBox.setEditable(true);
table.getColumn("A").setCellEditor(new DefaultCellEditor(comboBox)
{
private Object originalValue;
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column)
{
originalValue = value;
return super.getTableCellEditorComponent(table, value, isSelected, row, column);
}
@Override
public boolean stopCellEditing()
{
JComboBox comboBox = (JComboBox)getComponent();
DefaultComboBoxModel comboModel = (DefaultComboBoxModel) comboBox.getModel();
Object editingValue = getCellEditorValue();
// Needed because your TableModel is empty
if (editingValue == null)
return super.stopCellEditing();
int selectedIndex = comboModel.getIndexOf(editingValue);
// Selecting item from model
if (! (selectedIndex == -1))
return super.stopCellEditing();
// Confirm addition of new value
int result = JOptionPane.showConfirmDialog(
comboBox.getParent(),
"Add (" + editingValue + ") to table?",
"Update Model",
JOptionPane.YES_NO_OPTION);
if (result == JOptionPane.YES_OPTION)
{
comboBox.addItem(editingValue);
return super.stopCellEditing();
}
else
{
comboBox.removeItem(editingValue);
comboBox.setSelectedItem(originalValue);
return false;
}
}
});
// pack and show frame
frame.pack();
frame.setVisible(true);
}
}
Ok, I've done some changes and I think I got something working. If it is not the best practice and you got a better implementation, please post an answer.
Edit: Do not follow the example below! Following kleopatra's comments it is a wrong implementation. I'm leaving it here so you know how not to do it.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.DefaultCellEditor;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
public class TestComboCellEditor {
public static void main(String[] args) {
TestComboCellEditor test = new TestComboCellEditor();
test.go();
}
public void go() {
//create the frame
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// create and add a tabbed pane to the frame
JTabbedPane tabbedPane = new JTabbedPane();
frame.getContentPane().add(tabbedPane);
//create a table and add it to a scroll pane in a new tab
final JTable table = new JTable(new DefaultTableModel(new Object[]{"A", "B"}, 5));
JScrollPane scrollPane = new JScrollPane(table);
tabbedPane.addTab("test", scrollPane);
// create a simple JComboBox and set is as table cell editor on column A
Object[] comboElements = {"aaaaa1", "aaaaaa2", "b"};
final JComboBox comboBox = new JComboBox(comboElements);
comboBox.setEditable(true);
table.getColumn("A").setCellEditor(new DefaultCellEditor(comboBox) {
@Override
public boolean stopCellEditing() {
if (comboBox.isEditable()) {
DefaultComboBoxModel comboModel = (DefaultComboBoxModel) comboBox.getModel();
String selectedItem = (String) comboModel.getSelectedItem();
int selectedIndex = comboModel.getIndexOf(selectedItem);
if (!(selectedIndex == -1)) {
comboBox.actionPerformed(new ActionEvent(this, selectedIndex, "blabla"));
} else if (selectedItem != null) {
// missing code - adding new info to a custom JComboBox model and to alter info inside a custom table model
}
}
return super.stopCellEditing();
}
});
comboBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// the selected item exists as an Option inside the ComboBox
if (e.getActionCommand().equals("blabla")) {
DefaultComboBoxModel comboModel = (DefaultComboBoxModel) comboBox.getModel();
String selectedItem = (String) comboModel.getSelectedItem();
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
int selectedRow = table.getSelectedRow();
int selectedColumn = table.getSelectedColumn();
tableModel.setValueAt(selectedItem, selectedRow, selectedColumn);
}
}
});
// pack and show frame
frame.pack();
frame.setVisible(true);
}
}