2016-04-09 2 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); должен выполнять эту работу, не так ли?

Не следует ли сортировать таблицу после каждой модификации/добавления/удаления даже при запуске?

ответ

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>, который передается единым блоком к TableModel с помощью process().

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

Смежные вопросы