2009-06-15 15 views
6

He descubierto cómo ordenar un JTable correctamente, pero no puedo encontrar la forma de conseguir que actualice automáticamente el orden cuando se cambia una celda de la tabla. En este momento, tengo este código (ciertamente largo), basado principalmente en el tutorial de Java How to Use Tables. He resaltado los cambios que hice con // ADDED. En este caso, los valores recién agregados se clasifican correctamente, pero cuando entro para editar un valor, no parece recurrir, aunque llamo al fireTableCellUpdated?clasificación en vivo de JTable

En resumen, ¿cómo puedo obtener una tabla para volver a ordenar cuando cambia el valor de un dato en el modelo?

/* 
* Copyright (c) 1995 - 2008 Sun Microsystems, Inc. All rights reserved. 
* See the standard BSD license. 
*/ 

package components; 

/* 
* TableSortDemo.java requires no other files. 
*/ 

import java.awt.Dimension; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import java.util.ArrayList; 

import javax.swing.BoxLayout; 
import javax.swing.JButton; 
import javax.swing.JFrame; 
import javax.swing.JOptionPane; 
import javax.swing.JPanel; 
import javax.swing.JScrollPane; 
import javax.swing.JTable; 
import javax.swing.table.AbstractTableModel; 

public class TableSortDemo extends JPanel { 
    private boolean DEBUG = false; 

    public TableSortDemo() { 
     super(); 
     setLayout(new BoxLayout(TableSortDemo.this, BoxLayout.PAGE_AXIS)); 
     final MyTableModel m = new MyTableModel(); 
     JTable table = new JTable(m); 
     table.setPreferredScrollableViewportSize(new Dimension(500, 70)); 
     table.setFillsViewportHeight(true); 
     table.setAutoCreateRowSorter(true); 

     //Create the scroll pane and add the table to it. 
     JScrollPane scrollPane = new JScrollPane(table); 

     //Add the scroll pane to this panel. 
     add(scrollPane); 

     // ADDED: button to add a value 
     JButton addButton = new JButton("Add a new value"); 
     addButton.addActionListener(new ActionListener() { 
      public void actionPerformed(ActionEvent e) { 
       m.addValue(
         JOptionPane.showInputDialog(
           TableSortDemo.this, "Value?")); 
      } 
     }); 

     // ADDED button to change a value 
     JButton setButton = new JButton("Change a value"); 
     setButton.addActionListener(new ActionListener() { 
      /* (non-Javadoc) 
      * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) 
      */ 
      public void actionPerformed(ActionEvent e) { 
       m.setValueAt(
         JOptionPane.showInputDialog(
           TableSortDemo.this, "Value?"), 
         Integer.parseInt(
           JOptionPane.showInputDialog(
             TableSortDemo.this, "Which?")), 0); 
      } 
     }); 
     add(addButton); 
     add(setButton); 
    } 

    class MyTableModel extends AbstractTableModel { 
     private static final long serialVersionUID = -7053335255134714625L; 
     private String[] columnNames = {"Column"}; 
     // ADDED data as mutable ArrayList 
     private ArrayList<String> data = new ArrayList<String>(); 

     public MyTableModel() { 
      data.add("Anders"); 
      data.add("Lars"); 
      data.add("Betty"); 
      data.add("Anna"); 
      data.add("Jon"); 
      data.add("Zach"); 
     } 

     // ADDED 
     public void addValue(Object v) { 
      data.add(v.toString()); 
      int row = data.size() - 1; 
      fireTableRowsInserted(row, row); 
     } 

     public int getColumnCount() { 
      return columnNames.length; 
     } 

     public int getRowCount() { 
      return data.size(); 
     } 

     public String getColumnName(int col) { 
      return columnNames[col]; 
     } 

     public Object getValueAt(int row, int col) { 
      return data.get(row) + " " + row; 
     } 

     /* 
     * JTable uses this method to determine the default renderer/ 
     * editor for each cell. If we didn't implement this method, 
     * then the last column would contain text ("true"/"false"), 
     * rather than a check box. 
     */ 
     public Class<String> getColumnClass(int c) { 
      return String.class; 
     } 

     /* 
     * Don't need to implement this method unless your table's 
     * editable. 
     */ 
     public boolean isCellEditable(int row, int col) { 
      //Note that the data/cell address is constant, 
      //no matter where the cell appears onscreen. 
      if (col < 2) { 
       return false; 
      } else { 
       return true; 
      } 
     } 

     /* 
     * Don't need to implement this method unless your table's 
     * data can change. 
     */ 
     public void setValueAt(Object value, int row, int col) { 
      if (DEBUG) { 
       System.out.println("Setting value at " + row + "," + col 
            + " to " + value 
            + " (an instance of " 
            + value.getClass() + ")"); 
      } 

      data.set(row, value.toString()); 

      // ADDED: uncommented this line, despite warnings to the contrary 
      fireTableCellUpdated(row, col); 

      if (DEBUG) { 
       System.out.println("New value of data:"); 
       printDebugData(); 
      } 
     } 

     private void printDebugData() { 
      int numRows = getRowCount(); 
      int numCols = getColumnCount(); 

      for (int i=0; i < numRows; i++) { 
       System.out.print(" row " + i + ":"); 
       for (int j=0; j < numCols; j++) { 
        System.out.print(" " + data.get(i)); 
       } 
       System.out.println(); 
      } 
      System.out.println("--------------------------"); 
     } 
    } 

    /** 
    * Create the GUI and show it. For thread safety, 
    * this method should be invoked from the 
    * event-dispatching thread. 
    */ 
    private static void createAndShowGUI() { 
     //Create and set up the window. 
     JFrame frame = new JFrame("TableSortDemo"); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 

     //Create and set up the content pane. 
     TableSortDemo newContentPane = new TableSortDemo(); 
     newContentPane.setOpaque(true); //content panes must be opaque 
     frame.setContentPane(newContentPane); 

     //Display the window. 
     frame.pack(); 
     frame.setVisible(true); 
    } 

    public static void main(String[] args) { 
     //Schedule a job for the event-dispatching thread: 
     //creating and showing this application's GUI. 
     javax.swing.SwingUtilities.invokeLater(new Runnable() { 
      public void run() { 
       createAndShowGUI(); 
      } 
     }); 
    } 
} 

Respuesta

15

Este tomó una solución de dos pasos:

Primero tuve el tipo TableSorter sobre el cambio de datos, utilizando esta vez de autoCreateRowSorter:

sorter = new TableRowSorter<MyTableModel>(m); 
table.setRowSorter(sorter); 
sorter.setSortsOnUpdates(true); 

Entonces, tuve que cambiar el método de actualización de actualizar toda la tabla. El fireTableCellUpdated y la fireTableRowsUpdated sólo sería volver a dibujar las filas específicas que se han actualizado, no toda la tabla (es decir, se obtendría una entrada duplicada de aspecto que cambió tan pronto como se dibuja de nuevo más tarde. Por lo tanto, he cambiado

fireTableCellUpdated(row, col); 

a

fireTableRowsUpdated(0, data.size() - 1); 

y ahora ordena correctamente, incluso en los cambios de datos, y se conserva la selección.

-1

Hay varias cosas que debe hacer aquí.

  1. Dado que el modelo de mesa envuelve su colección, tiene que ser ordenable. Eso significa que su objeto (fila) tiene que implementar una interfaz comparable para que la colección se pueda ordenar correctamente.
  2. En su método setValueAt debe actualizar el atributo apropiado y recurrir a la colección usando Collections.sort. Entonces, obviamente, debe llamar a fireTableDataChanged para que la tabla sepa que necesita volver a dibujar.
  3. Lo mismo ocurre al sumar datos.
  4. Cuando se eliminan los datos, no tiene que recurrir, pero aún tiene que fireTableDataChanged
  5. Si su colección es demasiado grande, puede pensar en agregar datos al lugar apropiado inicialmente en lugar de recurrir.

Esperanza esto ayuda

+1

Salvo en este caso, yo no quiero que el TableModel que se encargue de la clasificación, yo quiero que eso ser hecho por el Clasificador en la vista. Tal como está, este método clasifica la tabla correctamente, maneja la inserción correctamente, pero no maneja los cambios correctamente. –

+0

¿Por qué no expone el método de clasificación en el modelo entonces? –

+0

IMO este será el diseño MVC apropiado. El modelo de tabla representa datos y debe encapsular operaciones relacionadas. Esto te permitirá disparar los eventos correctos. Al hacerlo público, puede llamar a la clasificación desde afuera. –

1

probablemente la manera más fácil de conseguirlo ordenadas sería llamar fireTableDataChanged() en lugar de fireTableCellUpdated().

+0

no, eso perdería la selección – kleopatra

4

es un long-standing bug on JTable, publicado en 2007 (sorprendido de que no es fijo, ni siquiera en JDK7)

Disparar una actualización en todas las filas es una solución rápida razonable si no degrada el rendimiento demasiado (debido a la activación frecuente de centros turísticos completos). Para los intrépidos, aquí hay una solución parcial en JTable: parcial, porque aún no se capturan todos los escenarios posibles.¿Cuál es la razón por la que nunca llegó a JXTable (o tal vez tenía otras prioridades a continuación :-)

public static class JTableRepaintOnUpdate extends JTable { 

    private UpdateHandler beforeSort; 

    @Override 
    public void sorterChanged(RowSorterEvent e) { 
     super.sorterChanged(e); 
     maybeRepaintOnSorterChanged(e); 
    } 

    private void beforeUpdate(TableModelEvent e) { 
     if (!isSorted()) return; 
     beforeSort = new UpdateHandler(e); 
    } 

    private void afterUpdate() { 
     beforeSort = null; 
    } 

    private void maybeRepaintOnSorterChanged(RowSorterEvent e) { 
     if (beforeSort == null) return; 
     if ((e == null) || (e.getType() != RowSorterEvent.Type.SORTED)) return; 
     UpdateHandler afterSort = new UpdateHandler(beforeSort); 
     if (afterSort.allHidden(beforeSort)) { 
      return; 
     } else if (afterSort.complex(beforeSort)) { 
      repaint(); 
      return; 
     } 
     int firstRow = afterSort.getFirstCombined(beforeSort); 
     int lastRow = afterSort.getLastCombined(beforeSort); 
     Rectangle first = getCellRect(firstRow, 0, false); 
     first.width = getWidth(); 
     Rectangle last = getCellRect(lastRow, 0, false); 
     repaint(first.union(last)); 
    } 

    private class UpdateHandler { 
     private int firstModelRow; 
     private int lastModelRow; 
     private int viewRow; 
     private boolean allHidden; 

     public UpdateHandler(TableModelEvent e) { 
      firstModelRow = e.getFirstRow(); 
      lastModelRow = e.getLastRow(); 
      convert(); 
     } 

     public UpdateHandler(UpdateHandler e) { 
      firstModelRow = e.firstModelRow; 
      lastModelRow = e.lastModelRow; 
      convert(); 
     } 

     public boolean allHidden(UpdateHandler e) { 
      return this.allHidden && e.allHidden; 
     } 

     public boolean complex(UpdateHandler e) { 
      return (firstModelRow != lastModelRow); 
     } 

     public int getFirstCombined(UpdateHandler e) { 
      if (allHidden) return e.viewRow; 
      if (e.allHidden) return viewRow; 
      return Math.min(viewRow, e.viewRow); 
     } 

     public int getLastCombined(UpdateHandler e) { 
      if (allHidden || e.allHidden) return getRowCount() - 1; 
      return Math.max(viewRow, e.viewRow); 

     } 

     private void convert() { 
      // multiple updates 
      if (firstModelRow != lastModelRow) { 
       // don't bother too much - calculation not guaranteed to do anything good 
       // just check if the all changed indices are hidden 
       allHidden = true; 
       for (int i = firstModelRow; i <= lastModelRow; i++) { 
        if (convertRowIndexToView(i) >= 0) { 
         allHidden = false; 
         break; 
        } 
       } 
       viewRow = -1; 
       return; 
      } 
      // single update 
      viewRow = convertRowIndexToView(firstModelRow); 
      allHidden = viewRow < 0; 
     } 

    } 

    private boolean isSorted() { 
     // JW: not good enough - need a way to decide if there are any sortkeys which 
     // constitute a sort or any effective filters 
     return getRowSorter() != null; 
    } 

    @Override 
    public void tableChanged(TableModelEvent e) { 
     if (isUpdate(e)) { 
      beforeUpdate(e); 
     } 
     try { 
      super.tableChanged(e); 
     } finally { 
      afterUpdate(); 
     } 
    } 

    /** 
    * Convenience method to detect dataChanged table event type. 
    * 
    * @param e the event to examine. 
    * @return true if the event is of type dataChanged, false else. 
    */ 
    protected boolean isDataChanged(TableModelEvent e) { 
     if (e == null) return false; 
     return e.getType() == TableModelEvent.UPDATE && 
      e.getFirstRow() == 0 && 
      e.getLastRow() == Integer.MAX_VALUE; 
    } 

    /** 
    * Convenience method to detect update table event type. 
    * 
    * @param e the event to examine. 
    * @return true if the event is of type update and not dataChanged, false else. 
    */ 
    protected boolean isUpdate(TableModelEvent e) { 
     if (isStructureChanged(e)) return false; 
     return e.getType() == TableModelEvent.UPDATE && 
      e.getLastRow() < Integer.MAX_VALUE; 
    } 

    /** 
    * Convenience method to detect a structureChanged table event type. 
    * @param e the event to examine. 
    * @return true if the event is of type structureChanged or null, false else. 
    */ 
    protected boolean isStructureChanged(TableModelEvent e) { 
     return e == null || e.getFirstRow() == TableModelEvent.HEADER_ROW; 
    } 

} 
+1

Guau, excelente respuesta. Esto fue una cooperativa de hace un año, y en mi caso, la actualización hacky ¡TODAS las cosas! solución terminó funcionando bien en cuanto al rendimiento. –