2009-06-15 8 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

これは、2段階のソリューションを取りましたテーブル全体を更新します。 fireTableCellUpdatedfireTableRowsUpdatedは、あなたが、すぐにそれが後で再描画されたように変化重複表示される項目を取得したいという意味(更新された特定の行ではなく、テーブル全体を再描画します。だから、私は

fireTableCellUpdated(row, col); 
を変更しました

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

から

、今でさえ、データの変更時に、適切にソートし、選択が保存されている。

-1

ここで行う必要のあることがいくつかあります。

  1. テーブルモデルはコレクションをラップするので、ソートする必要があります。つまり、コレクション(コレクション)を適切にソートできるように、オブジェクト(行)がComparableインターフェイスを実装する必要があります。
  2. setValueAtメソッドでは、適切な属性を更新し、Collections.sortを使用してコレクションを整理する必要があります。次に、明らかに、テーブルを再描画する必要があることを知らせるためにfireTableDataChangedを呼び出す必要があります。
  3. データの追加時に同じことが起こると考えられます。データが削除された場合
  4. あなたは頼らする必要がありますが、まだあなたのコレクションが大きいのであれば、あなたは適切な場所にデータを追加、当初の代わりに頼る考えることfireTableDataChanged
  5. する必要はありません。

希望これは)おそらくそれはソートを取得する最も簡単な方法は、(代わりにfireTableCellUpdatedのfireTableDataChanged()を呼び出すことであろう

+1

:-)その後、他の優先順位を持っていた、私は私がすることをしたい、のTableModelがソートのために責任を負うことにしたくありませんビューでソーターによって行われます。このメソッドは、テーブルを適切にソートし、挿入を適切に処理しますが、変更を適切に処理しません。 –

+0

モデルのソート方法を公開してみませんか? –

+0

IMOこれは適切なMVCデザインになります。表モデルはデータを表し、関連する操作をカプセル化する必要があります。これにより、適切なイベントを発生させることができます。パブリックにすることで、外部からソートを呼び出すことができます。 –

1

に役立ちます。私はむしろautoCreateRowSorterよりも、これを使用して、データ変更のTableSorterの並べ替えを持っていた

まず:

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

をその後、私は更新方法を変更しなければならなかった

+0

いいえ、それは選択を緩めるでしょう – kleopatra

4

そのlong-standing bug on JTable、2007年に報告された(それが固定されていないことを驚いていなくてもJDK7で)

すべての行で更新プログラムを起動することは、パフォーマンスが低下しない場合(頻繁に完全なリゾートをトリガーするため)、妥当な迅速な修正です。恐れのない人のために、ここではJTable - partialの部分的な修正があります。それは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; 
    } 

} 
+1

うわー、素晴らしい答え。これは数年前の協同組合で、私の場合、ハッキーなアップデート - すべてのこと!解決策はパフォーマンス上の問題を解決しました。 –