java swing jtable tablecelleditor

java - un error del método JTable.columnMoved



java jtable cell editor (3)

No veo ningún problema, ningún error, TableCellEditor se cancela correctamente al ordenar y reordenar columnas,

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(); } }); } }

javax.swing.JTable tiene un error, si ordenamos la tabla mientras se está editando una celda con valores nulos, y cuya clase de columna no tiene un constructor con el parámetro "nuevo Object [] {new String ()}", por ejemplo. Long.class, JTable lanzará una excepción.

Creo que porque javax.swing.JTable intenta eliminar el editor de la celda dos veces; Así que planeo anular el método JTable.columnMoved como:

@Override public void columnMoved(TableColumnModelEvent e) { if (isEditing() && !getCellEditor().stopCellEditing()) { if(getCellEditor() != null) { // In javax.swing.JTable, no this check point getCellEditor().cancelCellEditing(); } } repaint(); }

Sentí que no era lo suficientemente bueno, ya que no es amigable para los lectores de códigos, es posible que conozcan JTable y no me gustan mis subclases como esta. ¿Hay una mejor solución? Muchas gracias.

Al ejecutar los siguientes códigos, haga doble clic en una celda (no ingrese nada) y luego haga clic en el encabezado; se mostrará la excepción.

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 }


Puedo reproducir el problema con el código que me proporcionó. JTable#stopCellEditing implementación de 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(); }

Ingrese el primer if, donde se llama a super.stopCellEditing . El valor de retorno de esta llamada se ignora. A continuación, la llamada newInstance arroja una excepción, que se atrapa pero devuelve false , sin importar el valor de retorno de super.stopCellEditing . Esto me parece incorrecto Registraría el error con Oracle con el código que me proporcionó


El problema, analizado correctamente por @Robin, tiene varios aspectos:

  • stopCellEditing enciende incorrectamente una edición detenida incluso cuando devuelve falsa
  • stopCellEditing disparando dos veces si el constructor acepta un valor vacío

Mientras que el segundo es "justo" violar su contrato, el primero tiene graves consecuencias:

  • como se señala en la pregunta del OP: lanzar un NPE si el código del cliente intenta terminar realmente la edición, es decir, la primera parada y la cancelación de la falla. Irónicamente, la apariencia más visible se encuentra en jdk7 table.columnMoved, donde antes (hasta jdk6) removeEditor incorrecto se reemplaza por Right-Thing.
  • podría provocar daños en los datos si la edición comenzó con un valor válido que se elimina durante la edición: el evento editingStopped activa la tabla para reemplazar el valor válido en el modelo de tabla con nulo ...

Suficiente razón para solucionar en JXTable mediante una lógica diferente en GenericEditor (la corrección también se puede usar en un GenericEditor personalizado, sin acoplamiento a swingx: simplemente c & p core GenericEditor y reemplace su stopCellEditing con lo siguiente):

@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(); }