2016-04-09 3 views
2

Bei der Entwicklung eines kleinen Task-Managers habe ich festgestellt, dass Spalten nicht korrekt sortiert sind. Um Probleme mit meinem Programm zu verwerfen, habe ich eine Minimalversion erstellt, die aber immer noch nicht die eindeutige Spaltenfolge anordnet.setAutoCreateRowSorter sortiert die Tabellenspalte nach dem Update nicht korrekt

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

Ich habe mit einer Runnable + ScheduledExecutorService versucht und ein Timer + TimerTask nur um zu testen, ob es sich um ein Einfädeln Problem war, aber das Verhalten ist das gleiche. Ich habe auch die Java Tutorial Seite über das Thema gelesen. Da meine Tabelle nur Standardtypen verwendet, denke ich, dass eine einfache table.setAutoCreateRowSorter (true); sollte die Arbeit tun, sollte es nicht?

Sollte die Tabelle nicht nach jeder Änderung/Ergänzung/Entfernung sortiert werden?

Antwort

2

setSortsOnUpdates() Mit schlugen here von @trcs, ist die beste allgemeine Lösung, aber Sie können in der Lage sein, Aktualisierungen durch die Wahl der TableModelEvent zu Subklassen von AbstractTableModel zur Verfügung zu optimieren.

Das kritische Problem ist die Implementierung von setValueAt(). Wenn Sie fireTableRowsUpdated() statt fireTableRowUpdated() meinten, beachten Sie, dass die Parameter einen Bereich von Zeilen darstellen, nicht eine Zeile & Spalte. Da sich in diesem Fall "alle Zellenwerte in den Zeilen der Tabelle möglicherweise geändert haben", ruft das überarbeitete Beispiel unten auf. Ich habe auch das Modell geändert, um eine List<Integer> zu verwalten und normalisierte die Größe 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(); 
     }); 
    } 
} 

der Erkenntnis, dass dies nur ein Beispiel ist, optimiert die Variation und die Updates durch einen List<Integer> veröffentlichen, die en bloc zum TableModel über process() geben wird.

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

Vielen Dank für Ihre schnelle Antwort trashgod. Du hast recht, ich meinte fireTableRowsUpdated(), aber ich habe einen Fehler gemacht, als ich den Code geschrieben habe, tut mir leid. Der Punkt ist, dass fireTableRowsUpdated (rowIndex, rowIndex) und fireTableCellUpdated (rowIndex, columnIndex) beide die Spalte nicht korrekt sortieren können. In dem realen Programm ändern sich die meisten Tabellenzeilen von einer Iteration zur nächsten, so dass das Aufrufen von fireTableDataChanged() vollkommen sinnvoll ist. Aber ich wollte es nicht verwenden, wenn ich eine oder mehrere Zeilen auswähle, um bei jedem Update ein Signal an die Prozesse zu senden oder was auch immer die Auswahl verloren hat. Ich habe diesen Weg erkundet und zwei Formen gefunden, die Auswahl beizubehalten, aber es ist ein bisschen nervig und einer von ihnen bricht die Auswahl mit der Tastatur. Ich zeige die notwendigen Ergänzungen zum ursprünglichen Code als nächstes.

Die erste Form speichert, bevor die Auswahl des Modells zu modifizieren, und stellt sie wieder her nach jeder Aktualisierung:

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

Die zweite Form eine ListSelectionListener verwendet, eine KeyListener und ein Flag. Die Auswahl mit der Tastatur funktioniert nicht. Um ehrlich zu sein, weiß ich nicht, wie ich zu dieser Lösung gekommen bin. Es war wahrscheinlich zufällig:

Zum Glück heute habe ich einen einfachen Weg gefunden, um die Spalte richtig sortiert und behalten Sie die aktuelle Auswahl.Sie haben nur die folgenden Code hinzuzufügen:

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

Damit beide fireTableCellUpdated() und fireTableRowsUpdated() Arbeit als ich erwartet hatte. Nach meinem Verständnis wird setAutoCreateRowSorter() nur verwendet, um die Zeilen zu sortieren, wenn Sie auf den Tabellenkopf klicken.

Grüße.

+0

'setSortsOnUpdates (true)' ist wahrscheinlich in diesem Fall optimal; Ich würde 'KeyListener' vermeiden. – trashgod