2016-06-17 6 views
0

Wie sortiere ich JTable alphabetisch mit getrennten Zeilen, die das Vorkommen des nächsten Buchstabens anzeigen? Betrachten Sie zum Beispiel, wie Windows Media Player 12 Sorten verfolgt sie,Wie sortiert man JTable alphabetisch nach Anfangsbuchstaben? [siehe Bild zur Verdeutlichung]

Windows Media Player 12, example

Ein weiteres Beispiel für dieses Problem ist zum Beispiel, wie Macintosh ‚Finder‘ Sorten letzte Öffnung Anwendungen nach Datum. Ich kann nicht herausfinden, wie sie zusätzliche Reihe hinzufügten, die das Datum und die Linien zeigt, die das Zusammenbringen einschließen.

Irgendwelche Ideen, wie man dieses Problem angehen kann?

Macintosh, Finder, example

+0

J (X) TreeTable, vielleicht nicht frei Jide hat etwas Ähnliches – mKorbel

+0

Was ist J (X) TreeTable? Ist die Implementierung mit gewünschtem Aussehen schwer zu erreichen oder zeitaufwendig, wenn nur IDE und keine Drittanbieter-Quellen verwendet werden? – blueFalcon

+0

[J (X) TreeTable] (http://StackOverflow.com/a/7123228/714968), ich denke, dass MediaPalyer auch als TreeTable codiert ist – mKorbel

Antwort

1

Um die Tabellenzeilen erscheinen in einem anderen Ort als dem JTable sie in JTable die Methoden werden normalerweise erwartet haben Sie außer Kraft benötigen, um sind rowAtPoint, getCellRect und natürlich paintComponent. Um diese Methoden zu unterstützen, würde ich die Zeilen verfolgen, in denen Abschnittsüberschriften in einer sortierten Map vorkommen, und ich würde die Zeilenpositionen in einer zweiten sortierten Map verfolgen.

Ich würde diese Maps dann neu berechnen, wenn sich das Tabellenmodell oder die Sortierspalte ändert oder wenn sich die Tabelle aus irgendeinem Grund selbst validiert, was bedeutet, dass die Methoden tableChanged, sortedChanged und validate überschrieben werden.

Das Ergebnis ist nicht so lange, wie man erwarten könnte:

public class SectionedTable 
extends JTable { 
    private static final long serialVersionUID = 1; 

    private final NavigableMap<Integer, String> sectionHeadings = 
                 new TreeMap<>(); 

    private final NavigableMap<Integer, Integer> rowTopEdges = 
                 new TreeMap<>(); 

    // Used when calling SwingUtilities.layoutCompoundLabel. 
    private final Rectangle iconBounds = new Rectangle(); 
    private final Rectangle textBounds = new Rectangle(); 

    public SectionedTable() { 
     init(); 
    } 

    public SectionedTable(TableModel model) { 
     super(model); 
     init(); 
    } 

    private void init() 
    { 
     setShowGrid(false); 
     setAutoCreateRowSorter(true); 

     recomputeSections(); 
     recomputeRowPositions(); 
    } 

    private void recomputeSections() { 
     if (sectionHeadings == null) { 
      return; 
     } 

     sectionHeadings.clear(); 

     RowSorter<? extends TableModel> sorter = getRowSorter(); 
     if (sorter == null) { 
      return; 
     } 

     for (RowSorter.SortKey key : sorter.getSortKeys()) { 
      SortOrder order = key.getSortOrder(); 
      if (order != SortOrder.UNSORTED) { 
       int sortColumn = key.getColumn(); 

       String lastSectionStart = ""; 
       int rowCount = getRowCount(); 
       for (int row = 0; row < rowCount; row++) { 
        Object value = getValueAt(row, sortColumn); 
        if (value == null) { 
         value = "?"; 
        } 

        String s = value.toString(); 
        if (s.isEmpty()) { 
         s = "?"; 
        } 

        String sectionStart = s.substring(0, 
         s.offsetByCodePoints(0, 1)); 
        sectionStart = sectionStart.toUpperCase(); 

        if (!sectionStart.equals(lastSectionStart)) { 
         sectionHeadings.put(row, sectionStart); 
         lastSectionStart = sectionStart; 
        } 
       } 
       break; 
      } 
     } 
    } 

    private void recomputeRowPositions() { 
     if (rowTopEdges == null) { 
      return; 
     } 

     rowTopEdges.clear(); 

     int y = getInsets().top; 
     int rowCount = getRowCount(); 
     int rowHeight = getRowHeight(); 
     for (int row = 0; row < rowCount; row++) { 
      rowTopEdges.put(y, row); 
      y += getRowHeight(row); 
      if (sectionHeadings.containsKey(row)) { 
       y += rowHeight; 
      } 
     } 
    } 

    @Override 
    public void tableChanged(TableModelEvent event) { 
     recomputeSections(); 
     recomputeRowPositions(); 
     super.tableChanged(event); 
    } 

    @Override 
    public void sorterChanged(RowSorterEvent event) { 
     recomputeSections(); 
     recomputeRowPositions(); 
     super.sorterChanged(event); 
    } 

    @Override 
    public void validate() { 
     super.validate(); 
     recomputeRowPositions(); 
    } 

    @Override 
    public int rowAtPoint(Point location) { 
     Map.Entry<Integer, Integer> entry = rowTopEdges.floorEntry(location.y); 
     if (entry != null) { 
      int row = entry.getValue(); 
      return row; 
     } 
     return -1; 
    } 

    @Override 
    public Rectangle getCellRect(int row, 
           int column, 
           boolean includeSpacing) { 

     Rectangle rect = super.getCellRect(row, column, includeSpacing); 

     int sectionHeadingsAbove = sectionHeadings.headMap(row, true).size(); 
     rect.y += sectionHeadingsAbove * getRowHeight(); 

     return rect; 
    } 

    @Override 
    public void paintComponent(Graphics g) { 
     super.paintComponent(g); 

     boolean ltr = getComponentOrientation().isLeftToRight(); 

     int rowHeight = getRowHeight(); 
     FontMetrics metrics = g.getFontMetrics(); 
     int ascent = metrics.getAscent(); 
     for (Map.Entry<Integer, String> entry : sectionHeadings.entrySet()) { 
      int row = entry.getKey(); 
      String heading = entry.getValue(); 

      Rectangle bounds = getCellRect(row, 0, true); 
      bounds.y -= rowHeight; 
      bounds.width = getWidth(); 
      bounds.grow(-6, 0); 

      iconBounds.setBounds(0, 0, 0, 0); 
      textBounds.setBounds(0, 0, 0, 0); 
      String text = SwingUtilities.layoutCompoundLabel(this, 
       metrics, heading, null, 
       SwingConstants.CENTER, SwingConstants.LEADING, 
       SwingConstants.CENTER, SwingConstants.CENTER, 
       bounds, iconBounds, textBounds, 0); 

      g.drawString(text, textBounds.x, textBounds.y + ascent); 

      int lineY = textBounds.y + ascent/2; 
      if (ltr) { 
       g.drawLine(textBounds.x + textBounds.width + 12, lineY, 
            getWidth() - getInsets().right - 12, lineY); 
      } else { 
       g.drawLine(textBounds.x - 12, lineY, 
            getInsets().left + 12, lineY); 
      } 
     } 
    } 
} 

Sie diese Klasse anpassen kann eine Finder-Tabelle zu machen, indem wie die Überschriften aus den Daten abgeleitet werden. Das bloße Untersuchen einer Zelle mit getValueAt wird nicht ausreichen; Stattdessen müssen Sie translate the sorted row to the corresponding model row eingeben und das Datenobjekt für diese Zeile direkt aus dem TableModel abrufen. Dann können Sie die Zeilen nach Alter und nicht nach String-Daten vergleichen.

+0

Große Lösung! Allerdings kann ich nicht mit der SectionTable in der Tabelle nach unten scrollen. Auch wenn sich mein Tabellenmodell ändert, indem die Methode DefaultTableModel.setDataVector (Object [] [] dataVector, Object [] columnIdentifiers) aufgerufen wird, malt sich die Tabelle wieder in den ursprünglichen Bereich zurück. Irgendwelche Ideen, wie ich das beheben kann? – blueFalcon

+0

Zeigt Ihre Tabelle nach dem Aufruf von setDataVector noch eine Sortierspalte an? – VGR

+0

Nein, es kehrt zur ursprünglichen Reihenfolge zurück, unsortiert und alle Abschnitte (die Zeilen und der Buchstabe) sind verschwunden. Ich muss manuell auf den Tabellen-Header klicken, um ihn erneut zu sortieren. Auch beim Scrollen wird beim nächsten Buchstaben abgewürgt und es geht nicht weiter. Scrollen nach oben funktioniert jedoch gut. – blueFalcon

1

Ich würde Gruppe meine Daten alphabetisch und dann eine separate Tabelle in meine Daten enthalten für jeden Buchstaben erstellen.

Sie können die Anzeige des Headers mit einem TableCellRenderer anpassen, um das gewünschte Aussehen zu erhalten. Wenn Sie weitere Spalten hinzufügen möchten, wie in Ihren Beispielen gezeigt, können Sie den Header durch eine andere Komponente ersetzen.

public class MyTableFrame extends JFrame { 

    MyTableFrame() { 

     List<String> data = Arrays.asList(
      "Alpha1", 
      "Beta1", 
      "Alpha2", 
      "Charlie2", 
      "Alpha3", 
      "Beta2", 
      "Charlie1" 
     ); 
     // Sort alphabetically 
     data.sort(String::compareTo); 

     // Group by first letter 
     Map<Character, List<String>> collect = data.stream() 
       .collect(Collectors.groupingBy(string -> string.charAt(0))); 

     JPanel tablesContainer = new JPanel(new GridBagLayout()); 
     GridBagConstraints c = new GridBagConstraints(); 
     c.fill = GridBagConstraints.BOTH; 
     c.weightx = 1; 
     c.weighty = 0; 
     c.gridy = 0; 

     // Create a table for each Letter 
     collect.entrySet().forEach(mapEntry -> { 
      Character letter = mapEntry.getKey(); 
      List<String> startingWithLetter = mapEntry.getValue(); 
      TableModel tableModel = new TableModel(startingWithLetter, letter); 
      JTable table = new JTable(tableModel); 
      // Table header must be added separately since there is no scroll pane 
      JTableHeader tableHeader = table.getTableHeader(); 
      tablesContainer.add(tableHeader, c); 

      c.gridy++; 
      tablesContainer.add(table, c); 

      c.gridy++; 
     }); 

     add(tablesContainer); 
     setVisible(true); 
     pack(); 
    } 

    static class TableModel extends AbstractTableModel { 
     private List<String> data; 
     private Character columnName; 

     TableModel(List<String> data, Character columnName) { 
      this.data = data; 
      this.columnName = columnName; 
     } 

     @Override 
     public String getColumnName(int column) { 
      return columnName.toString(); 
     } 

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

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

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

    public static void main(String args[]) { 
     new MyTableFrame(); 
    } 
}