2009-06-15 43 views
6

我已經想出瞭如何讓JTable正確排序,但我無法弄清楚如何讓它在表格單元格更改時自動更新排序順序。現在,我有了這個(無可否認的)代碼,主要基於Java教程的How to Use Tables。我強調了我使用// ADDED所做的更改。在這種情況下,新增值的排序正確,但是當我進入編輯值時,儘管我打電話給fireTableCellUpdated,但它似乎並不起作用。JTable的實時排序

總之,當模型中的數據值發生變化時,如何獲得表來重新排序?

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

回答

15

這花了兩個步驟的解決方案:

首先,我對數據變化的tablesorter排序,通過使用這個,而不是autoCreateRowSorter

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

然後,我不得不改變更新方法更新整個表格。該fireTableCellUpdatedfireTableRowsUpdated將重繪進行了更新,具體的行,而不是整個表(這意味着你會得到,一旦它後來被重繪改變複本出現的條目。所以,我改變

fireTableCellUpdated(row, col); 

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

,現在它正確排序,即使在數據變化,並選擇將被保留。

-1

有幾件事你必須在這裏做。

  1. 由於表模型包裝您的集合,它必須是可排序的。這意味着您的對象(行)必須實現Comparable接口,以便收集可以正確排序。
  2. 在您的setValueAt方法中,您必須更新適當的屬性並使用Collections.sort來使用集合。然後,顯然,你必須調用fireTableDataChanged讓表知道它需要重繪。
  3. 假設在添加數據時會發生同樣的情況。
  4. 當數據被刪除時,您不必去度假,但仍然需要fireTableDataChanged
  5. 如果您的收藏很大,您可能會考慮將數據添加到適當的地方而不是訴諸手段。

希望這有助於

+1

除了在這種情況下,我不希望TableModel來負責整理,我希望這樣的由視圖中的分揀機完成。就目前而言,此方法正確排序表,正確處理插入,但不能正確處理更改。 – 2009-06-15 17:50:32

+0

爲什麼不直接在模型上展示排序方法呢? – 2009-06-15 18:51:51

+0

IMO這將是適當的MVC設計。表模型表示數據並應封裝相關操作。這將讓你發射適當的事件。通過公開,你可以打電話從外面排序。 – 2009-06-15 18:54:53

1

可能獲得最簡單的方法它整理將調用fireTableDataChanged()代替fireTableCellUpdated()。

4

它是一個long-standing bug on JTable,2007年報告(驚訝,這是不固定的,甚至在JDK7)

如果不降低性能太多(由於觸發頻繁完成度假村),在所有行上啓動更新是一個合理的快速修復。對於無所畏懼的人來說,這裏是對JTable的部分修正 - 部分是因爲還沒有捕獲所有可能的場景。這是原因,它從未進入JXTable(或者也許我有其他優先事項,然後:-)

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

}