2016-04-09 11 views
2

小さなタスクマネージャを開発する際に、列が正しくソートされていないことに気付きました。私のプログラムの問題を捨てるために、私は最小限のバージョンを作成しましたが、依然として一意の列を正しく注文することはできません。setAutoCreateRowSorterは更新後にテーブル列を正しくソートしません

import java.awt.BorderLayout; 
import java.util.List; 
import java.util.Random; 
import javax.swing.*; 
import javax.swing.table.AbstractTableModel; 

public class TableSortTest extends JFrame 
{ 
    private final JTable table; 
    private final ATableModel model; 

    public TableSortTest() 
    { 
     setDefaultCloseOperation (EXIT_ON_CLOSE); 
     setSize (1366, 768); 
     setLocationRelativeTo (null); 

     model = new ATableModel(); 
     table = new JTable(); 
     table.setFillsViewportHeight (true); 
     table.setAutoCreateRowSorter (true); 
     table.setModel (model); 

     add (new JScrollPane (table), BorderLayout.CENTER); 

     setVisible (true); 

     Worker worker = new Worker(); 
     worker.execute(); 
    } 

    private class Pair 
    { 
     int index; 
     int value; 
    } 

    private class Worker extends SwingWorker <Void, Pair> 
    { 
     @Override 
     protected Void doInBackground() 
     { 
      while (!isCancelled()) 
      { 
       Random r = new Random(); 
       for (int i = 0; i < 100; i++) 
       { 
        int indice = getIndexInRange (0, 99); 
        Pair p = new Pair(); 
        p.index = indice; 
        p.value = Math.abs (r.nextInt()); 
        publish (p); 
       } 

       try 
       { 
        Thread.sleep (1000); 
       } 
       catch (InterruptedException ie) 
       { 
        ie.printStackTrace(); 
       } 
      } 

      return null; 
     } 

     @Override 
     public void process (List <Pair> items) 
     { 
      for (Pair p : items) 
      { 
       model.setValueAt (p.value, p.index, 0); 
      } 
     } 
    } 

    public static int getIndexInRange (int min, int max) 
    { 
     return (min + (int) (Math.random() * ((max - min) + 1))); 
    } 

    private class ATableModel extends AbstractTableModel 
    { 
     private final Integer [] data; 

     public ATableModel() 
     { 
      data = new Integer [100]; 

      Random r = new Random(); 

      for (int i = 0; i < 100; i++) 
      { 
       data [i] = Math.abs (r.nextInt()); 
      } 
     } 

     @Override 
     public int getColumnCount() 
     { 
      return 1; 
     } 

     @Override 
     public int getRowCount() 
     { 
      return data.length; 
     } 

     @Override 
     public Object getValueAt (int rowIndex, int columnIndex) 
     { 
      return data [rowIndex]; 
     } 

     @Override 
     public void setValueAt (Object value, int rowIndex, int columnIndex) 
     { 
      data [rowIndex] = (Integer) value; 
      fireTableRowUpdated (rowIndex, columnIndex); 
     } 

     @Override 
     public Class getColumnClass (int columnIndex) 
     { 
      return Integer.class; 
     } 

     @Override 
     public String getColumnName (int col) 
     { 
      return "Column"; 
     } 
    } 

    public static final void main (String [] args) 
    { 
     SwingUtilities.invokeLater (() -> 
     { 
      try 
      { 
       new TableSortTest(); 
      } 
      catch (Exception e) 
      { 
       e.printStackTrace(); 
      } 
     }); 
    } 
} 

それはスレッドの問題だったが、動作は同じである場合、私はScheduledExecutorService + RunnableとちょうどテストするTimer + TimerTaskで試してみました。また、そのテーマについてのJavaチュートリアルのページも読んでいます。私のテーブルは標準タイプしか使用していないので、単純なtable.setAutoCreateRowSorter (true);は仕事をしなければならないと思います。

修正/追加/削除のたびにテーブルをソートしないでください。 setSortsOnUpdates()を使用して

答えて

2

は、@trcshereを提案し、最良の一般解であるが、あなたはAbstractTableModelのサブクラスに利用できるTableModelEventの選択によって更新を最適化することができるかもしれません。

重要な問題はsetValueAt()の実装です。 を意味する場合は、fireTableRowUpdated()の代わりに、ではなく、の行&の列でなければなりません。この場合、「表の行のすべてのセル値が変更された可能性があるため」、下の改訂例ではfireTableDataChanged()が呼び出されます。また、List<Integer>を管理するようにモデルを変更し、サイズを正規化したN。これは単なる例であることを認識

image

import java.awt.BorderLayout; 
import java.awt.Dimension; 
import java.util.ArrayList; 
import java.util.List; 
import java.util.Random; 
import javax.swing.*; 
import javax.swing.table.AbstractTableModel; 

/** @see https://stackoverflow.com/a/36522182/230513 */ 
public class TableSortTest extends JFrame { 

    private final JTable table; 
    private final ATableModel model; 

    public TableSortTest() { 
     setDefaultCloseOperation(EXIT_ON_CLOSE); 

     model = new ATableModel(); 
     table = new JTable(model){ 
      @Override 
      public Dimension getPreferredScrollableViewportSize() { 
       return new Dimension(200, 500); 
      } 
     }; 
     table.setFillsViewportHeight(true); 
     table.setAutoCreateRowSorter(true); 

     add(new JScrollPane(table), BorderLayout.CENTER); 
     pack(); 
     setLocationRelativeTo(null); 
     setVisible(true); 

     Worker worker = new Worker(); 
     worker.execute(); 
    } 

    private class Pair { 

     int index; 
     int value; 
    } 

    private class Worker extends SwingWorker<Void, Pair> { 

     private static final int N = 100; 
     private final Random r = new Random(); 

     @Override 
     protected Void doInBackground() { 
      while (!isCancelled()) { 
       for (int i = 0; i < N; i++) { 
        int index = r.nextInt(N); 
        Pair p = new Pair(); 
        p.index = index; 
        p.value = Math.abs(r.nextInt()); 
        publish(p); 
       } 

       try { 
        Thread.sleep(1000); 
       } catch (InterruptedException ie) { 
        ie.printStackTrace(); 
       } 
      } 

      return null; 
     } 

     @Override 
     public void process(List<Pair> items) { 
      for (Pair p : items) { 
       model.setValueAt(p.value, p.index, 0); 
      } 
     } 
    } 

    private class ATableModel extends AbstractTableModel { 

     private static final int N = 100; 
     private final List<Integer> data = new ArrayList<>(N); 

     public ATableModel() { 
      final Random r = new Random(); 
      for (int i = 0; i < N; i++) { 
       data.add(Math.abs(r.nextInt())); 
      } 
     } 

     @Override 
     public int getColumnCount() { 
      return 1; 
     } 

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

     @Override 
     public Object getValueAt(int rowIndex, int columnIndex) { 
      return data.get(rowIndex); 
     } 

     @Override 
     public void setValueAt(Object value, int rowIndex, int columnIndex) { 
      data.set(rowIndex, (Integer) value); 
      fireTableDataChanged(); 
     } 

     @Override 
     public Class getColumnClass(int columnIndex) { 
      return Integer.class; 
     } 

     @Override 
     public String getColumnName(int col) { 
      return "Column"; 
     } 
    } 

    public static final void main(String[] args) { 
     SwingUtilities.invokeLater(() -> { 
      new TableSortTest(); 
     }); 
    } 
} 

、以下の変化がprocess()介しTableModelに一括エンが渡されList<Integer>を発行することにより更新を最適化します。あなたの迅速な答えtrashgodため

import java.awt.BorderLayout; 
import java.awt.Dimension; 
import java.util.ArrayList; 
import java.util.List; 
import java.util.Random; 
import javax.swing.*; 
import javax.swing.table.AbstractTableModel; 

/** 
* @ see https://stackoverflow.com/a/36522182/230513 
*/ 
public class TableSortTest extends JFrame { 

    private final JTable table; 
    private final ATableModel model; 

    public TableSortTest() { 
     setDefaultCloseOperation(EXIT_ON_CLOSE); 

     model = new ATableModel(); 
     table = new JTable(model) { 
      @Override 
      public Dimension getPreferredScrollableViewportSize() { 
       return new Dimension(200, 500); 
      } 
     }; 
     table.setFillsViewportHeight(true); 
     table.setAutoCreateRowSorter(true); 

     add(new JScrollPane(table), BorderLayout.CENTER); 
     pack(); 
     setLocationRelativeTo(null); 
     setVisible(true); 

     Worker worker = new Worker(); 
     worker.execute(); 
    } 

    private class Worker extends SwingWorker<List<Integer>, List<Integer>> { 

     private static final int N = 100; 
     private final Random r = new Random(); 
     private final List<Integer> data = new ArrayList<>(N); 

     @Override 
     protected List<Integer> doInBackground() throws Exception { 
      while (!isCancelled()) { 
       data.clear(); 
       for (int i = 0; i < N; i++) { 
        data.add(Math.abs(r.nextInt())); 
       } 
       publish(data); 
       try { 
        Thread.sleep(1000); 
       } catch (InterruptedException ie) { 
        ie.printStackTrace(System.err); 
       } 
      } 
      return data; 
     } 

     @Override 
     protected void process(List<List<Integer>> chunks) { 
      for (List<Integer> chunk : chunks) { 
       model.update(chunk); 
      } 
     } 
    } 

    private class ATableModel extends AbstractTableModel { 

     private List<Integer> data = new ArrayList<>(); 

     public void update(List<Integer> data) { 
      this.data = data; 
      fireTableDataChanged(); 
     } 

     @Override 
     public int getColumnCount() { 
      return 1; 
     } 

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

     @Override 
     public Object getValueAt(int rowIndex, int columnIndex) { 
      return data.get(rowIndex); 
     } 

     @Override 
     public Class getColumnClass(int columnIndex) { 
      return Integer.class; 
     } 

     @Override 
     public String getColumnName(int col) { 
      return "Column"; 
     } 
    } 

    public static final void main(String[] args) { 
     SwingUtilities.invokeLater(() -> { 
      new TableSortTest(); 
     }); 
    } 
} 
2

感謝。あなたが正しいです、私はfireTableRowsUpdated()を意味しましたが、私はコードを書いたときに間違いを犯しました、申し訳ありません。要点は、fireTableRowsUpdated (rowIndex, rowIndex)fireTableCellUpdated (rowIndex, columnIndex)が両方とも列を正しくソートできないということです。実際のプログラムでは、ほとんどのテーブル行が1回の反復から次の反復に変わるので、fireTableDataChanged()を呼び出すのが完璧です。しかし、プロセスにシグナルを送信するために1つまたは複数の行を選択した場合、またはすべての更新で選択が失われたものが何であれ、私はそれを使用したくなかった。私はこの方法を模索し、選択を保存する2つの形式を見つけましたが、少し迷惑で、そのうちの1つがキーボードで選択を破ります。次に元のコードに必要な追加を示します。

最初の形式は、モデルを変更する前に、選択を保存し、すべての更新後に復元:

... 
private class Worker extends SwingWorker <Void, Pair> 
{ 
    private int [] selectedRows; 

    @Override 
    protected Void doInBackground() 
    { 
     while (!isCancelled()) 
     { 
      // Save the selection before modifying the model 
      int x = table.getSelectedRowCount(); 
      if (x > 0) 
      { 
       selectedRows = new int [x]; 
       int [] tableSelection = table.getSelectedRows(); 

       for (int i = 0; i < x; i++) 
       { 
        selectedRows [i] = table.convertRowIndexToModel (tableSelection [i]); 
       } 
      } 

      Random r = new Random(); 
      for (int i = 0; i < table.getRowCount(); i++) 
      { 
       int indice = getIndexInRange (0, table.getRowCount() - 1); 
       Pair p = new Pair(); 
       p.index = indice; 
       p.value = Math.abs (r.nextInt()); 
       publish (p); 
      } 

      // If I put the code to restore the selection here, it doesn't work... 
      try 
      { 
       Thread.sleep (1000); 
      } 
      catch (InterruptedException ie) 
      { 
       ie.printStackTrace(); 
      } 
     } 

     return null; 
    } 

    @Override 
    public void process (List <Pair> items) 
    { 
     for (Pair p : items) 
     { 
      model.setValueAt (p.value, p.index, 1); 
     } 

     // Restore the selection on every update 
     if (selectedRows != null && selectedRows.length > 0) 
     { 
      for (int i = 0; i < selectedRows.length; i++) 
      { 
       table.addRowSelectionInterval (table.convertRowIndexToView (selectedRows [i]), table.convertRowIndexToView (selectedRows [i])); 
      } 
     } 
    } 
} 
... 

第二形態はListSelectionListenerKeyListener、フラグを使用します。キーボードによる選択は機能しません。正直言って、私はこの解決方法をどうやって得たのか分かりません。私が正しくソートされた列を取得し、現在の選択を維持するための簡単な方法を発見した

public class TableSortTestSolucionConSelectionListener extends JFrame implements KeyListener 
{ 
    ... 
    private boolean ctrlOrShiftDown = false; 
    private int [] selectedRows; 

    @Override 
    public void keyPressed (KeyEvent e) 
    { 
     ctrlOrShiftDown = e.isControlDown() || e.isShiftDown(); 
    } 

    @Override 
    public void keyReleased (KeyEvent e) 
    { 
     ctrlOrShiftDown = e.isControlDown() || e.isShiftDown(); 
    } 

    @Override 
    public void keyTyped (KeyEvent e) 
    { 
     ctrlOrShiftDown = e.isControlDown() || e.isShiftDown(); 
    } 

    public TableSortTestSolucionConSelectionListener() 
    { 
     ... 
     ListSelectionListener lsl = new ListSelectionListener() 
     { 
      @Override 
      public void valueChanged (ListSelectionEvent e) 
      { 
       if (!e.getValueIsAdjusting()) 
       { 
        if (!ctrlOrShiftDown) 
        { 
         int x = table.getSelectedRowCount(); 
         if (x > 0) 
         { 
          selectedRows = new int [x]; 
          int [] tableSelection = table.getSelectedRows(); 

          for (int i = 0; i < x; i++) 
          { 
           selectedRows [i] = table.convertRowIndexToModel (tableSelection [i]); 
          } 
         } 
        } 

        // Disable the listener to avoid infinite recursion 
        table.getSelectionModel().removeListSelectionListener (this); 

        if (selectedRows != null && selectedRows.length > 0) 
        { 
         for (int i = 0; i < selectedRows.length; i++) 
         { 
          table.addRowSelectionInterval (table.convertRowIndexToView (selectedRows [i]), table.convertRowIndexToView (selectedRows [i])); 
         } 
        } 

        table.getSelectionModel().addListSelectionListener (this); 
       } 
      } 
     }; 

     table.getSelectionModel().addListSelectionListener (lsl); 
     ...  
    } 

幸い今日:それはおそらく偶然でした。この両方fireTableCellUpdated()と私は予想通りfireTableRowsUpdated()仕事で

TableRowSorter trs = (TableRowSorter) table.getRowSorter(); 
trs.setSortsOnUpdates (true); 

:あなたは、あなたのコードに以下を追加する必要があります。私の理解では、setAutoCreateRowSorter()は、テーブルヘッダーをクリックすると行を並べ替えるためにのみ使用されます。

ご挨拶。

+0

この場合、おそらく 'setSortsOnUpdates(true)'が最適です。私は 'KeyListener'を避けるだろう。 – trashgod