2009-06-15 6 views
6

Ich habe herausgefunden, wie man eine JTable richtig sortiert bekommen, aber ich kann nicht herausfinden, wie es die Sortierreihenfolge automatisch aktualisieren, wenn eine Tabellenzelle geändert wird. Im Moment habe ich diesen (zugegeben langen) Code, hauptsächlich basierend auf dem Java Tutorial How to Use Tables. Ich habe die Änderungen hervorgehoben, die ich mit // ADDED vorgenommen habe. In diesem Fall werden neu hinzugefügte Werte richtig sortiert, aber wenn ich einen Wert editiere, scheint es keinen Ausweg zu geben, obwohl ich fireTableCellUpdated anrufe?Live-Sortierung von JTable

Kurz gesagt, wie kann ich eine Tabelle neu sortieren, wenn sich ein Datenwert im Modell ändert?

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

Antwort

15

Dieses eine zweistufige Lösung nahm:

Zuerst habe ich die Art auf Datenänderung Tablesorter hatte, durch diese eher als autoCreateRowSorter mit:

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

Dann musste ich die Update-Methode ändern Aktualisieren Sie die gesamte Tabelle. Die fireTableCellUpdated und die fireTableRowsUpdated würden nur die spezifischen Reihen neu zu zeichnen, die aktualisiert wurden, nicht die gesamte Tabelle (dh Sie ein Duplikat erscheinenden Eintrag bekommen würden, die so schnell verändert, wie es später neu gezeichnet wurde. Also änderte ich

fireTableCellUpdated(row, col); 

zu

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

und jetzt sortiert es richtig, auch bei Datenänderungen und die Auswahl beibehalten.

-1

Es gibt mehrere Dinge, die Sie hier tun müssen.

  1. Da das Tabellenmodell Ihre Sammlung umschließt, muss es sortierbar sein. Das bedeutet, dass Ihr Objekt (Zeile) eine vergleichbare Schnittstelle implementieren muss, damit die Sammlung richtig sortiert werden kann.
  2. In Ihrer setValueAt-Methode müssen Sie das entsprechende Attribut aktualisieren und die Sammlung mithilfe von Collections.sort umsortieren. Dann müssen Sie natürlich fireTableDataChanged aufrufen, um der Tabelle mitzuteilen, dass sie neu gezeichnet werden muss.
  3. Das gleiche gilt für das Hinzufügen von Daten.
  4. Wenn Daten entfernt werden, müssen Sie nicht fireTableDataChanged
  5. Wenn Ihre Sammlung zu groß ist, können Sie darüber nachdenken, Daten an geeigneter Stelle zu ergänzen, anstatt neu zu sortieren.

hoffe, das hilft

+1

Außer in diesem Fall, ich will nicht das Tablemodel für die Sortierung verantwortlich sein, ich will die durch den Sorter in der Ansicht erfolgen. So wie es aussieht, sortiert diese Methode die Tabelle korrekt, behandelt die Einfügung richtig, aber nicht richtig mit den Änderungen. –

+0

Warum entlarven Sie dann nicht einfach die Sortiermethode auf dem Modell? –

+0

IMO wird das richtige MVC-Design sein. Das Tabellenmodell stellt Daten dar und sollte verwandte Vorgänge kapseln. Dadurch können Sie geeignete Ereignisse auslösen. Wenn Sie es öffentlich machen, können Sie die Sortierung von außen aufrufen. –

1

wahrscheinlich der einfachste Weg, um es zu bekommen sortiert wäre fireTableDataChanged() aufzurufen, statt fireTableCellUpdated().

+0

nein, das würde die Auswahl verlieren – kleopatra

4

Sein ein long-standing bug on JTable, 2007 (erstaunt, dass es nicht festgelegt ist, nicht einmal in JDK7) berichtete

Das Brennen eines Updates für alle Zeilen ist eine sinnvolle und schnelle Lösung, wenn die Leistung nicht zu stark beeinträchtigt wird (weil häufig komplette Resorts ausgelöst werden). Für die Furchtlosen, hier ist eine Teillösung für JTable - teilweise, weil noch nicht alle möglichen Szenarien erfasst sind.Welche der Grund wird es nie in JXTable (oder vielleicht hatte ich andere Prioritäten dann :-)

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, große Antwort. Das war vor Jahren Co-Op, und in meinem Fall, das Hacky Update-ALL-the-things! Die Lösung endete in der Performance gut. –