JTable.columnMoved method error

javax.swing.JTable has an error if we sort the table while a cell with a zero value is being edited, and whose column class does not have a constructor with "new Object [] {new String ()}", for example. Long.class, JTable throws an exception.

I think that since javax.swing.JTable is trying to remove the cell editor twice; Therefore, 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 it wasn’t good enough, since it is not suitable for reading code, they can know JTable well and do not like my subclass like this. Is there a better solution? Thank you very much.

When you run the following codes, double-click one cell (do not enter anything), and then click the title, an exception will appear.

 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 } 
+4
source share
3 answers

I can reproduce the problem with the code you provided. I blame the implementation of JTable#stopCellEditing

  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 calling newInstance throws an exception that is caught but returns false , regardless of whether the return value was super.stopCellEditing . That sounds wrong to me. I would log an error with Oracle with the code you provided

+4
source

I see no problem, no error, TableCellEditor is canceled correctly when sorting and reordering columns,

 import java.awt.*; import javax.swing.*; import javax.swing.table.DefaultTableModel; public class TableWithTimer { private static final long serialVersionUID = 1L; private JFrame frame = new JFrame(); private JScrollPane scroll = new JScrollPane(); private JTable myTable; private String[] head = {"One", "Two", "Three", "Four", "Five", "Six"}; private Object[][] data = {{null, null, null, null, null, null}, {null, null, null, null, null, null}, {null, null, null, null, null, null}, {null, null, null, null, null, null}, {null, null, null, null, null, null}, {null, null, null, null, null, null}, {null, null, null, null, null, null}}; private DefaultTableModel model; public TableWithTimer() { model = new DefaultTableModel(data, head) { private static final long serialVersionUID = 1L; @Override public Class<?> getColumnClass(int colNum) { switch (colNum) { case 0: return Integer.class; case 1: return Double.class; case 2: return Long.class; case 3: return Boolean.class; default: return String.class; } } }; myTable = new JTable(model); myTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); myTable.setGridColor(Color.gray); myTable.setFillsViewportHeight(true); myTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); myTable.setAutoCreateRowSorter(true); scroll.setViewportView(myTable); frame.add(scroll, BorderLayout.CENTER); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLocation(100, 100); frame.pack(); frame.setVisible(true); } public static void main(String args[]) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { TableWithTimer tableWithTimer = new TableWithTimer(); } }); } } 
+3
source

The problem correctly analyzed by @Robin has several aspects:

  • stopCellEditing does not start editing correctly Stopped even if false is returned
  • stopCellEditing is run twice if the constructor is empty

While the second "just" violates his contract, the first has serious consequences:

  • as pointed out in the OP question: throwing an NPE if the client code is trying to really stop editing, i.e. the first stop and cancel the failure. Oddly enough, the most noticeable view is jdk7 table.columnMoved - where previously (before jdk6) the incorrect removeEditor is replaced with the right key.
  • can lead to data corruption if editing started with a valid value that is deleted when editing: the Stopped edit event runs the table to replace the valid value in the Model table with a zero value ...

There is enough reason to fix it in JXTable using a different logic in GenericEditor (the fix can be used in regular GenericEditor, without connection with swingx: just c & p core GenericEditor and replace its stopCellEditing with the following):

 @Override public boolean stopCellEditing() { String s = (String) super.getCellEditorValue(); // JW: changed logic to hack around (core!) Issue #1535-swingx // don't special case empty, but string contructor: // if so, by-pass reflection altogether if (constructor.getDeclaringClass() == String.class) { value = s; } else { // try instantiating a new Object with the string try { value = constructor.newInstance(new Object[] { s }); } catch (Exception e) { ((JComponent) getComponent()).setBorder(new LineBorder( Color.red)); return false; } } return super.stopCellEditing(); } 
+1
source

Source: https://habr.com/ru/post/1447877/


All Articles