2009-06-15 12 views
6

Ho capito come ottenere un ordinamento JTable correttamente, ma non riesco a capire come ottenerlo per aggiornare automaticamente l'ordinamento quando viene cambiata una cella di tabella. In questo momento, ho questo (certamente lungo) codice, per lo più basato su quello nel How to Use Tables di Java Tutorial. Ho evidenziato le modifiche che ho apportato con // ADDED. In questo caso, i valori appena aggiunti vengono ordinati correttamente, ma quando passo a modificare un valore, non sembra ricorrere, anche se chiamo fireTableCellUpdated?Ordinamento in tempo reale di JTable

In breve, come è possibile ottenere una tabella per riordinare quando un valore di dati cambia nel modello?

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

risposta

15

questo ha preso una soluzione in due fasi:

Prima ho avuto il tipo tablesorter sul cambiamento dei dati, utilizzando questo piuttosto che autoCreateRowSorter:

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

Poi, ho dovuto cambiare il metodo di aggiornamento aggiorna l'intera tabella. Il fireTableCellUpdated ed il fireTableRowsUpdated sarebbe ridisegnare solo le specifiche file che sono stati aggiornati, non l'intera tabella (il che significa che si otterrebbe una voce duplicata di aspetto che ha cambiato, non appena è stato ridisegnato più tardi. Così, ho cambiato

fireTableCellUpdated(row, col); 

a

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

e ora ordina correttamente, anche su modifiche dei dati, e la selezione è conservata.

-1

Ci sono molte cose che devi fare qui.

  1. Poiché il modello di tabella include la raccolta, deve essere ordinabile. Ciò significa che l'oggetto (riga) deve implementare l'interfaccia Comparable in modo che la raccolta possa essere ordinata correttamente.
  2. Nel metodo setValueAt è necessario aggiornare l'attributo appropriato e ricorrere alla raccolta utilizzando Collections.sort. Quindi, ovviamente, devi chiamare fireTableDataChanged per far sapere alla tabella che è necessario ridisegnare.
  3. La stessa cosa si suppone accada con l'aggiunta di dati.
  4. Quando i dati vengono rimossi non è necessario ricorrere, ma devono ancora fireTableDataChanged
  5. Se la tua raccolta è troppo grande, potresti pensare di aggiungere i dati al posto appropriato inizialmente invece di ricorrere.

Spero che questo aiuti

+1

Tranne che in questo caso, non voglio il TableModel di essere responsabile per l'ordinamento, voglio che a essere fatto dal Sorter nella vista. Così com'è, questo metodo ordina la tabella correttamente, gestisce correttamente l'inserimento, ma non gestisce correttamente le modifiche. –

+0

Perché non esporre semplicemente il metodo di ordinamento sul modello? –

+0

IMO questo sarà il corretto design MVC. Il modello di tabella rappresenta i dati e dovrebbe incapsulare le operazioni correlate. Questo ti consentirà di attivare eventi appropriati. Rendendolo pubblico puoi chiamare l'ordinamento dall'esterno. –

1

probabilmente il modo più semplice per farlo ordinati sarebbe quello di chiamare fireTableDataChanged() al posto di fireTableCellUpdated().

+0

no, che perderebbe la selezione – kleopatra

4

sua una long-standing bug on JTable, riportata nel 2007 (stupito che non è fisso, nemmeno in jdk7)

L'esecuzione di un aggiornamento su tutte le righe è una soluzione rapida ragionevole se non riduce eccessivamente le prestazioni (a causa dell'attivazione di località complete frequenti). Per gli intrepidi, ecco una correzione parziale su JTable - parziale, perché non tutti gli scenari possibili vengono catturati.Che è il motivo per cui non ha mai fatto in JXTable (o forse ho avuto altre priorità, allora :-)

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

Wow, ottima risposta. Questa è stata una co-op anni fa, e nel mio caso, l'aggiornamento hacky-ALL-the-things! la soluzione ha finito per funzionare solo in termini di prestazioni. –