a bug of JTable.columnMoved method
java, jtable, swing, tablecelleditor
Solution
I can reproduce the issue with the code you provided. I blame the `JTable#stopCellEditing` implementation
public boolean stopCellEditing() {
String s = (String)super.getCellEditorValue();
// Here we are dealing with the case where a user
// has deleted the string value in a cell, possibly
// after a failed validation. Return null, so that
// they have the option to replace the value with
// null or use escape to restore the original.
// For Strings, return "" for backward compatibility.
if ("".equals(s)) {
if (constructor.getDeclaringClass() == String.class) {
value = s;
}
super.stopCellEditing();
}
try {
value = constructor.newInstance(new Object[]{s});
}
catch (Exception e) {
((JComponent)getComponent()).setBorder(new LineBorder(Color.red));
return false;
}
return super.stopCellEditing();
}
You enter the first if, where `super.stopCellEditing` is called. The return value of this call is ignored. Then the `newInstance` call throws an exception, which is catched but then `false` is returned, no matter what the return value was of `super.stopCellEditing`. This sounds incorrect to me. I would log the bug with Oracle with the code you provided
Problem
javax.swing.JTable has a bug, if we sort the table while a null valued cell is editing, and whose column class does not have a constructor with "new Object[] { new String() }" parameter, eg. Long.class, the JTable will throw an exception. I think that because javax.swing.JTable tries to remove the cell editor twice; So I plan to override the JTable.columnMoved method as: ``` @Override public void columnMoved(TableColumnModelEvent e) { if (isEditing() && !getCellEditor().stopCellEditing()) { if(getCellEditor() != null) { // In javax.swing.JTable, no this check point getCellEditor().cancelCellEditing(); } } repaint(); } ``` I felt that's not good enough, since it's not friendly for code readers, they may know JTable well, and do not like my sub classes like this. Is there a better solution? Thanks a lot. When running the following codes, double click one cell (do not input anything) and then click the header, the exception will show up. ``` public class NewJFrame extends javax.swing.JFrame { public NewJFrame() { initComponents(); } @SuppressWarnings("unchecked") // <editor-fold defaultstate="collapsed" desc="Generated Code"> private void initComponents() { jScrollPane1 = new javax.swing.JScrollPane(); jTable1 = new javax.swing.JTable(); setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); jTable1.setModel(new javax.swing.table.DefaultTableModel( new Object [][] { {null, null}, {null, null}, {null, null}, {null, null} }, new String [] { "Title 1", "Title 2" } ) { Class[] types = new Class [] { java.lang.Long.class, java.lang.Long.class }; public Class getColumnClass(int columnIndex) { return types [columnIndex]; } }); jScrollPane1.setViewportView(jTable1); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 375, javax.swing.GroupLayout.PREFERRED_SIZE) .addContainerGap(15, Short.MAX_VALUE)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 275, javax.swing.GroupLayout.PREFERRED_SIZE) .addContainerGap(14, Short.MAX_VALUE)) ); pack(); }// </editor-fold> /** * @param args the command line arguments */ public static void main(String args[]) { /* Set the Nimbus look and feel */ //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) "> /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel. * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html */ try { for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) { if ("Nimbus".equals(info.getName())) { javax.swing.UIManager.setLookAndFeel(info.getClassName()); break; } } } catch (ClassNotFoundException ex) { java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); } catch (InstantiationException ex) { java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); } catch (IllegalAccessException ex) { java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); } catch (javax.swing.UnsupportedLookAndFeelException ex) { java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); } //</editor-fold> /* Create and display the form */ java.awt.EventQueue.invokeLater(new Runnable() { public void run() { new NewJFrame().setVisible(true); } }); } // Variables declaration - do not modify private javax.swing.JScrollPane jScrollPane1; private javax.swing.JTable jTable1; // End of variables declaration } ```