2011-05-03 11 views
6

Estoy intentando crear un verificador de entrada simple para una JTable. Terminé anulando el método: editingStopped(). El problema es que el evento no incluye información sobre la celda que se ha actualizado.JTable Input Verifier

Este es mi "pseudo código":

If (user finished editing a cell) { 
    Check if cell`s value is "1" or "0" or "-" (Karnaugh-Veitch) 
    If (check = false) 
     setValue (cell, ""); 
    } 

El primero que probé fue esta aquí:

table.getModel().addTableModelListener(new TableModelListener() { 
      @Override 
      public void tableChanged(TableModelEvent e) { 
       inputVerify (e.getColumn(), e.getFirstRow()); 
      } 
}); 

    public void inputVerify (int column, int row) { 
     boolean verified = true; 
     String field = table.getValueAt(row, column).toString(); 

     if (field != null && field.length() == 1) { 
      if (!(field.charAt(0) == '0' || field.charAt(0) == '1' || field.charAt(0) == '-')) 
       verified = false; 
     } 
     else { 
      verified = false; 
     } 

     if (!verified) { 
      table.getModel().setValueAt("", row, column); 
      java.awt.Toolkit.getDefaultToolkit().beep(); 
     } 

     System.out.println ("Column = " + column + " Row = " + row + " Value = " + table.getValueAt(row, column) +" Verified = "+verified); 
    } 

Pero esto termina con una: StackOverflow Excepción. Supongo que el problema es que: setValueAt (..) desencadena otro evento tableChanged() y se está generando un bucle infinito.

Ahora, he intentado esto aquí:

table.getDefaultEditor(Object.class).addCellEditorListener(new CellEditorListener() { 

     // called when editing stops 
     public void editingStopped(ChangeEvent e) { 

      // print out the value in the TableCellEditor 
      System.out.println(((CellEditor) e.getSource()).getCellEditorValue().toString()); 

     } 

     public void editingCanceled(ChangeEvent e) { 
      // whatever 
     } 
    }); 

Pero como se puede ver que sólo puede recuperar el nuevo valor de la celda, no las "coordenadas". Necesito llamar al método setValueAt (..), pero no sé cómo obtener las coordenadas de la celda.

¿O hay una manera más simple de crear un verificador de entrada?

Saludos Ioannis K.

+0

no, que no quieren llamar setValueAt, por lo que no necesita las coordenadas :-) – kleopatra

Respuesta

12

En primer lugar: la validación de entrada en la edición JTable no está bien soportado. Un par de comentarios

  • tableChanged en un TableModelListener no es un buen lugar para hacer la validación, en ese punto en el tiempo el cambio ya ha ocurrido (el modelo notifica a sus oyentes del hecho)
  • como consecuencia, cualquiera que sea método de validación (verificar) gancho que eliges, nunca hablas con el modelo, terminarás en un bucle infinito (como has visto)
  • CellEditorListeners proporcionados por la aplicación son bastante inútiles porque a) no hay garantía acerca de secuencia de notificación (JTable puede o no haber actualizado el modelo) b) el ciclo de vida de un editor está mal definido

Después de todo (incompleto, lamentablemente ;-) no-nos, un poco de esperanza: la mejor opción es implementar un CellEditor personalizado que valide en stopCellCellEditing: si el nuevo valor no es válido, devuelva falso y opcionalmente proporcionar un comentario visual de error. Echar un vistazo a JTable.GenericEditor para tener una idea de cómo podría hacerse

+0

Gracias Kleopatra esta aclaración agradable y útil! Me di cuenta de que un verificador de entrada, en mi caso, es muy incómodo. No solo para mí, sino también para los usuarios. Decidí agregar/modificar el CellEditor. Finalmente terminé con un DefaultCellEditor y un JComboBox como parámetro de constructor . Cada vez que un usuario quiere editar una celda, aparece un JComboBox con las entradas "0" "1" y "-". De esta manera no hay necesidad de buscar una entrada válida y es más cómodo para el usuario :-) –

4

Lo que funcionó para mí (punta 'o el sombrero para kleopatra):

private class CellEditor extends DefaultCellEditor { 

    InputVerifier verifier = null; 

    public CellEditor(InputVerifier verifier) { 
     super(new JTextField()); 
     this.verifier = verifier; 

    } 

    @Override 
    public boolean stopCellEditing() { 
     return verifier.verify(editorComponent) && super.stopCellEditing(); 
    } 

} 

// ... 

private class PortVerifier extends InputVerifier { 

    @Override 
    public boolean verify(JComponent input) { 
     boolean verified = false; 
     String text = ((JTextField) input).getText(); 
     try { 
      int port = Integer.valueOf(text); 
      if ((0 < port) && (port <= 65535)) { 
       input.setBackground(Color.WHITE); 
       verified = true; 
      } else { 
       input.setBackground(Color.RED); 
      } 
     } catch (NumberFormatException e) { 
      input.setBackground(Color.RED); 
     } 
     return verified; 
    } 
} 

// ... 

table.getColumn("Port").setCellEditor(new CellEditor(new PortVerifier())); 
+0

gracias :-) Solo un poco de cuidado: estrictamente hablando, su implementación de verificar no es del todo correcta ya que no está permitida tener efectos secundarios. Aunque probablemente te salgas con la tuya aquí, ya que el efecto secundario es establecer el fondo solamente. – kleopatra

+0

@kleopatra: gracias, eso es bueno saberlo. – Dave

0

hmm, podría haber una solución más simple a esto. Por favor, intente esto, funcionó para mí. La clave es recordar el último elemento seleccionado y luego realizar la validación en el elemento actual. Si la entrada es incorrecta, puede retroceder al último elemento seleccionado y notificar al usuario al respecto. La retrotracción se realiza utilizando EventQueue.invokeLater (...), evitando llamadas recursivas a los oyentes.

private final DefaultTableModel dtm = new DefaultTableModel(); 
private final JTable table = new JTable(dtm); 
private final Object[] lastItem; 
private final AtomicInteger lastIndex = new AtomicInteger(-1); 
private final ItemValidator validator = new ItemValidator(); 


public YourConstructor() { 

    lastItem = new Object[table.getColumnCount()]; 


    //store last value of selected table item in an array. 
    table.addMouseListener(new MouseAdapter(){ 
     public void mouseClicked(MouseEvent evt){ 
      lastIndex.set(table.getSelectedRow()); 
      int row = lastIndex.get(); 
      for(int i=0;i<lastItem.length;i++){ 
       lastItem[i] = table.getValueAt(row, i); 
      } 
     } 
    }); 

    //for input validation, and database update. 
    dtm.addTableModelListener(new TableModelListener(){ 

     @Override 
     public void tableChanged(TableModelEvent e) { 
      switch(e.getType()){ 
      case TableModelEvent.INSERT: 
       System.out.println("insert"); 
       break; 
      case TableModelEvent.UPDATE: 
       validateUpdate(); 
       break; 
      case TableModelEvent.DELETE: 
       System.out.println("delete"); 
       break; 
      default: 
       break; 
      } 
     } 

    }); 
} 

public void validateUpdate(){ 
    String item; 
    for(int i=0;i<lastItem.length;i++) 
    { 
     item = (String)table.getValueAt(lastIndex.get(), i); 
     if(i>1 && i<lastItem.length)//column range to be checked 
     { 
      if(!validator.hasNumericText(item)) 
      { 
       final int col = i; 
       final Object lastObject = lastItem[i]; 
       final int row = lastIndex.get(); 

       //the most important part, to avoid StackOverflow 
       //by using EventQueue, you avoid looping around 
       //the TableModelListener. 
       EventQueue.invokeLater(new Runnable(){ 
        public void run(){ 
         table.setValueAt(lastObject, row, col); 
        } 
       }); 

       System.out.println("Error at " + i); 
       break; 
      } 
     } 
    } 
}