2016-04-09 77 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 + RunnableTimer + TimerTask只是爲了測試,如果它是一個線程的問題,但行爲是一樣的。我還閱讀了有關該主題的Java教程頁面。鑑於我的表只使用標準類型,我認爲一個簡單的table.setAutoCreateRowSorter (true);應該完成這項工作,不是嗎?

不應該在每次修改/添加/刪除之後對錶進行排序,甚至會被解僱嗎?

回答

2

使用setSortsOnUpdates(),建議here通過@trcs,是最好的通用的解決方案,但你可以通過TableModelEvent可供選擇,以優化更新的AbstractTableModel子類。

關鍵問題是執行setValueAt()。如果您的意思是fireTableRowsUpdated()而不是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(); 
     }); 
    } 
} 

認識到這僅僅是一個例子,下面的變化通過發佈List<Integer>,其經由傳遞process()整塊TableModel優化更新。

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

感謝您的快速回答trashgod。你是對的,我的意思是fireTableRowsUpdated(),但是當我寫代碼的時候我犯了一個錯誤,對不起。問題是fireTableRowsUpdated (rowIndex, rowIndex)fireTableCellUpdated (rowIndex, columnIndex)都無法正確地對列進行排序。在真正的程序中,大多數表格行從一次迭代到另一次迭代都會發生變化,因此調用fireTableDataChanged()是非常合理的。但我不想使用它,因爲如果我選擇一個或多個行來向進程發送信號,或者每次更新都會丟失選擇。我已經探索過這種方式,並發現了兩種保留選擇的形式,但它有點煩人,其中一個用鍵盤打破了選擇。我接下來會展示原始代碼的必要補充。

第一種形式保存所述選擇修改模型之前和每次更新之後恢復它:

... 
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])); 
      } 
     } 
    } 
} 
... 

第二種形式使用了ListSelectionListener,一個KeyListener,以及標誌。使用鍵盤進行選擇不起作用。說實話,我不知道我是如何得到這個解決方案的。這可能是偶然的:

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

幸運的是,今天我發現了一種簡單的方法來正確排序列並保持當前選擇。你只需要添加以下代碼:

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

有了這個既fireTableCellUpdated()並如我所料fireTableRowsUpdated()工作。據我的理解,setAutoCreateRowSorter()僅用於在單擊表格標題時對行進行排序。

問候。

+0

'setSortsOnUpdates(true)'在這種情況下可能是最優的;我會避免'KeyListener'。 – trashgod