I want to use a JComboBox
as a cell editor in a JXTreeTable
. It works fine with a standard DefaultCellEditor
(i.e. with a click count to start equal to 2).
Now I want the column to be editable on only one click. So I added a cellEditor.setClickCountToStart(1);
statement to my code.
Here is my SSCCE:
import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.DefaultCellEditor;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import org.jdesktop.swingx.JXTreeTable;
import org.jdesktop.swingx.treetable.DefaultMutableTreeTableNode;
import org.jdesktop.swingx.treetable.DefaultTreeTableModel;
public class TestCellEditorForJXTreeTable {
/** The JXTreeTable */
JXTreeTable treeTable;
/** The model */
DefaultTreeTableModel treeTableModel;
/** Constructor */
public TestCellEditorForJXTreeTable() {
treeTable = new JXTreeTable();
treeTableModel = new DefaultTreeTableModel() {
@Override
public String getColumnName(int column) {
switch (column) {
case 0:
return "A";
case 1:
return "B";
}
return null;
}
@Override
public Object getValueAt(Object node, int column) {
switch (column) {
case 0:
return ((DefaultMutableTreeTableNode) node).getUserObject();
case 1:
return "Value in B";
}
return null;
}
@Override
public int getColumnCount() {
return 2;
}
@Override
public boolean isCellEditable(Object node, int column) {
return column == 1;
}
};
treeTable.setTreeTableModel(treeTableModel);
}
public static void main(String[] args) {
TestCellEditorForJXTreeTable test = new TestCellEditorForJXTreeTable();
// Root node
DefaultMutableTreeTableNode root = new DefaultMutableTreeTableNode("root");
test.treeTableModel.setRoot(root);
// New nodes/rows
DefaultMutableTreeTableNode node = new DefaultMutableTreeTableNode("child_node");
test.treeTableModel.insertNodeInto(node, root, 0);
DefaultMutableTreeTableNode node2 = new DefaultMutableTreeTableNode("child_node2");
test.treeTableModel.insertNodeInto(node2, root, 1);
// Showing the frame
showTable(test.treeTable);
// Setting the cell editor
DefaultCellEditor cellEditor = new DefaultCellEditor(new JComboBox(new String[]{"1", "2", "3"}));
cellEditor.setClickCountToStart(1);
test.treeTable.getColumn(1).setCellEditor(cellEditor);
}
/** Shows a JXTreeTable in a frame */
private static void showTable(JXTreeTable table) {
JFrame frame = new JFrame("Testing cell editor for JXTreeTable");
frame.setPreferredSize(new Dimension(640, 480));
frame.setLayout(new BorderLayout());
frame.add(table, BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
But now it looks pretty ugly:
When I click on an editable cell it opens the JComboBox
popup menu (Great! It's what I was expecting!), but this popup menu is immediately closed (Erf!). It flashes. I have to click a second time on the selected cell to get it definitively opened.
The problem repeats each time I select another cell in the editable column.
How could I get the JComboBox
popup menu really opened after the first click?
Thanks.
Here is the same example, but using JTable
. The JComboBox
popup menu does not flash.
import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.DefaultCellEditor;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
public class TestCellEditorForJTable {
/** The JTable */
JTable table;
/** The model */
DefaultTableModel tableModel;
/** Constructor */
public TestCellEditorForJTable() {
table = new JTable();
tableModel = new DefaultTableModel(new String[] {"A", "B"}, 0) {
@Override
public boolean isCellEditable(int row, int column) {
return column == 1;
}
};
table.setModel(tableModel);
}
public static void main(String[] args) {
TestCellEditorForJTable test = new TestCellEditorForJTable();
// New rows
test.tableModel.insertRow(0, new String[] {"Value1 in A", "Value1 in B"});
test.tableModel.insertRow(1, new String[] {"Value2 in A", "Value2 in B"});
// Showing the frame
showTable(test.table);
// Setting the cell editor
DefaultCellEditor cellEditor = new DefaultCellEditor(new JComboBox(new String[]{"1", "2", "3"}));
cellEditor.setClickCountToStart(1);
test.table.getColumnModel().getColumn(1).setCellEditor(cellEditor);
}
/** Shows a table in a frame */
private static void showTable(JTable table) {
JFrame frame = new JFrame("Testing cell editor for JTable");
frame.setPreferredSize(new Dimension(640, 480));
frame.setLayout(new BorderLayout());
frame.add(table, BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
And I forgot to mention that I'm using Java 1.6.
Using the ContainerListener
and the FocusListener
of the kleopatra's answer, and running the same execution flow, I get the following output with the JXTreeTable
SSCCE:
// first click
24.01.2014 13:10:59 my.pkg.TestCellEditorForJXTreeTable$2 componentAdded
INFO: java.awt.event.ContainerEvent[COMPONENT_ADDED...JXTreeTable...
24.01.2014 13:10:59 my.pkg.TestCellEditorForJXTreeTable$3 focusGained
INFO: java.awt.FocusEvent[FOCUS_GAINED...JXTreeTable...
24.01.2014 13:10:59 my.pkg.TestCellEditorForJXTreeTable$2 componentRemoved
INFO: java.awt.event.ContainerEvent[COMPONENT_REMOVED...JXTreeTable...
24.01.2014 13:10:59 my.pkg.TestCellEditorForJXTreeTable$3 focusLost
INFO: java.awt.FocusEvent[FOCUS_LOST...JXTreeTable...
// second click
24.01.2014 13:11:02 my.pkg.TestCellEditorForJXTreeTable$2 componentAdded
INFO: java.awt.event.ContainerEvent[COMPONENT_ADDED...JXTreeTable...
24.01.2014 13:11:02 my.pkg.TestCellEditorForJXTreeTable$3 focusGained
INFO: java.awt.FocusEvent[FOCUS_GAINED...JXTreeTable...
24.01.2014 13:11:02 my.pkg.TestCellEditorForJXTreeTable$2 componentRemoved
INFO: java.awt.event.ContainerEvent[COMPONENT_REMOVED...JXTreeTable...
24.01.2014 13:11:02 my.pkg.TestCellEditorForJXTreeTable$3 focusLost
INFO: java.awt.FocusEvent[FOCUS_LOST...JXTreeTable...
Tricky bugger - and I think it's indeed a core issue.
Let's first define exactly what/when it is happening: take the plain table example (btw: +1 for the nice and concise SSCCE!)
Digging reveals the probable reason: it's an out-of-order focusLost received after the combo was added again as editing component. To see, register a containerListener to the table and a focusListener to the combo and print the events
ContainerListener containerL = new ContainerListener() {
@Override
public void componentRemoved(ContainerEvent e) {
LOG.info("" + e);
}
@Override
public void componentAdded(ContainerEvent e) {
LOG.info("" + e);
}
};
table.addContainerListener(containerL);
FocusListener focusL = new FocusListener() {
@Override
public void focusGained(FocusEvent e) {
LOG.info("" + e);
// following line is a hack around: force the popup open
// ((JComboBox) cellEditor.getComponent()).setPopupVisible(true);
}
@Override
public void focusLost(FocusEvent e) {
LOG.info("" + e);
}
};
cellEditor.getComponent().addFocusListener(focusL);
The output:
// first click
24.01.2014 12:13:44 org.jdesktop.swingx.table.TestCellEditorForJTable$2 componentAdded
INFO: java.awt.event.ContainerEvent[COMPONENT_ADDED,child=null] on javax.swing.JTable...
24.01.2014 12:13:44 org.jdesktop.swingx.table.TestCellEditorForJTable$3 focusGained
INFO: java.awt.FocusEvent[FOCUS_GAINED,permanent,opposite=javax.swing.JTable
// second click
24.01.2014 12:13:49 org.jdesktop.swingx.table.TestCellEditorForJTable$2 componentRemoved
INFO: java.awt.event.ContainerEvent[COMPONENT_REMOVED,child=null] on javax.swing.JTable
24.01.2014 12:13:49 org.jdesktop.swingx.table.TestCellEditorForJTable$2 componentAdded
INFO: java.awt.event.ContainerEvent[COMPONENT_ADDED,child=null] on javax.swing.JTable
// here's the problem: focusLost _after_ added again
24.01.2014 12:13:49 org.jdesktop.swingx.table.TestCellEditorForJTable$3 focusLost
INFO: java.awt.FocusEvent[FOCUS_LOST,permanent,opposite=javax.swing.JTable
24.01.2014 12:13:49 org.jdesktop.swingx.table.TestCellEditorForJTable$3 focusGained
INFO: java.awt.FocusEvent[FOCUS_GAINED,permanent,opposite=javax.swing.JTable
A quick hack could be to force the popup open in the focusListener. Didn't check for side-effects, though.
Interesting fact
If you pick a value from the combobox then the editor works as expected (next click on an editable cell will open the combobox of that cell as you wish), but if you close the combobox without selecting a value then it behaves as you describe. So the issue seems to be the combobox doesn't stop editing until you select a value or focus in some other component. Consequently the first click to another cell makes its own editor request focus instead of start editing.
Looking closer at DefaultCellEditor implementation the problem is only an ActionListener is attached to the combobox causing a fireEditingStopped() call through the EditorDelegate when an item is selected but nothing happens when the combobox is closed or cancelled without selecting a value:
public DefaultCellEditor(final JComboBox comboBox) {
editorComponent = comboBox;
comboBox.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
delegate = new EditorDelegate() {...}
comboBox.addActionListener(delegate); // delegate is the ActionListener
}
protected class EditorDelegate implements ActionListener, ItemListener, Serializable {
...
public void actionPerformed(ActionEvent e) {
DefaultCellEditor.this.stopCellEditing(); // This will finally call fireEditingStopped();
}
}
Make your own TableCellEditor using a combobox as editor and attach a PopupMenuListener to call fireEditingStopped() or fireEditingCanceled() as needed. For instance:
class ComboBoxEditor extends AbstractCellEditor implements TableCellEditor {
private JComboBox editor;
private int clickCountToStart = 2;
private Object selectedValue;
public ComboBoxEditor(Object[] selectableValues) {
editor = new JComboBox(selectableValues);
editor.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
selectedValue = editor.getSelectedItem();
ComboBoxEditor.this.fireEditingStopped();
}
});
editor.addPopupMenuListener(new PopupMenuListener() {
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
// Nothing to do here, it's not relevant to your purpose
}
@Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
ComboBoxEditor.this.fireEditingStopped();
}
@Override
public void popupMenuCanceled(PopupMenuEvent e) {
ComboBoxEditor.this.fireEditingCanceled();
}
});
}
public void setClickCountToStart(int clickCountToStart) {
this.clickCountToStart = clickCountToStart;
}
public int getClickCountToStart() {
return clickCountToStart;
}
// Rest of implementation is up to you, look into DefaultCellEditor implementation
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With