# JTable: Zeilen sortieren, aber bestimmte Zeilen fest lassen



## Br4twurscht (5. Okt 2009)

Hallo Community,

ich möchte meine JTable vom TableRowSorter sortieren lassen, jedoch die letzte Zeile fest lassen, bzw. fixieren. Dummerweise gibts nur ne Methode setSortable() für Spalten.

Erst dachte ich, ich könne nen Comparator schreiben, aber der hat ja dann keine Kenntnis über die konkreten Zeilenindizes. Naja, und als Notlösung würde mir nur noch einfallen, die Methode convertRowIndexToModel() zu überschreiben. Die scheint er beim zeichnen der Tabelle aufzurufen um das Mapping abzufragen.

Aber gibts da nich noch ne bessere Lösung (mal abgesehen vom entfernen der Zeile, die fest bleiben soll)?


lg
Br4twurscht


----------



## Ebenius (6. Okt 2009)

Ich hab eben ein bisschen rumgespielt. So in etwa würde ich es lösen: 
	
	
	
	





```
/* (@)JTableSortingFun.java */

/* Copyright 2009 Sebastian Haufe

 * Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       [url]http://www.apache.org/licenses/LICENSE-2.0[/url]

 * Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License. */

package com.ebenius;

import java.awt.BorderLayout;
import java.util.*;

import javax.swing.*;
import javax.swing.event.RowSorterEvent;
import javax.swing.event.RowSorterListener;
import javax.swing.table.*;

/**
 * Test on customized sorting.
 * 
 * @version $Revision$ as of $Date$
 * @author Sebastian Haufe
 */
public class JTableSortingFun {

  static class AccumulatingIntegerTableModel extends DefaultTableModel {

    private static final long serialVersionUID = 1L;
    private int[] sum = { 0, 0 };

    AccumulatingIntegerTableModel() {
      super(new String[] { "Top Level Sorting", "A", "B" }, 0);
      addRow(new Object[] { null, null, null });
    }

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

    @Override
    public Object getValueAt(int row, int column) {
      if (row == getRowCount() - 1) {
        switch (column) {
        case 0:
          return Integer.valueOf(Integer.MAX_VALUE);
        default:
          return Integer.valueOf(sum[column - 1]);
        }
      }
      return super.getValueAt(row, column);
    }

    @Override
    public boolean isCellEditable(int row, int column) {
      return row < getRowCount() - 1 && super.isCellEditable(row, column);
    }

    @Override
    public void setValueAt(Object aValue, int row, int column) {
      if (row < getRowCount() - 1) {
        super.setValueAt(aValue, row, column);
      }
      if (column > 0) {
        summarize(column);
      }
    }

    private void summarize(int column) {
      int acc = 0;
      final int lastRow = getRowCount() - 1;
      for (int row = 0; row < lastRow; row++) {
        acc += ((Integer) getValueAt(row, column)).intValue();
      }
      if (acc != sum[column - 1]) {
        sum[column - 1] = acc;
        fireTableCellUpdated(lastRow, column);
      }

    }

    @SuppressWarnings("unchecked")
    @Override
    public void addRow(Vector rowData) {
      insertRow(Math.max(0, getRowCount() - 1), rowData);
    }

    @SuppressWarnings("unchecked")
    @Override
    public void insertRow(int row, Vector rowData) {
      super.insertRow(row, rowData);
      for (int column = 1; column < getColumnCount(); column++) {
        summarize(column);
      }
    }
  }

  static class CustomRowSorter extends RowSorter<TableModel>
    implements RowSorterListener {

    private DefaultRowSorter<TableModel, Integer> sorter;

    CustomRowSorter(TableModel model) {
      sorter = new TableRowSorter<TableModel>(model);
      sorter.addRowSorterListener(this);
      sorter.setSortKeys(Collections.singletonList(new SortKey(0,
            SortOrder.ASCENDING)));
      sorter.setSortsOnUpdates(true);
    }

    @Override
    public int convertRowIndexToModel(int index) {
      return sorter.convertRowIndexToModel(index);
    }

    @Override
    public int convertRowIndexToView(int index) {
      return sorter.convertRowIndexToView(index);
    }

    @Override
    public TableModel getModel() {
      return sorter.getModel();
    }

    @Override
    public int getModelRowCount() {
      return sorter.getModelRowCount();
    }

    @Override
    public int getViewRowCount() {
      return sorter.getViewRowCount();
    }

    // -----------------------------------------------------------------------
    // Sorter controlling
    // -----------------------------------------------------------------------

    @Override
    public List<? extends SortKey> getSortKeys() {
      final LinkedList<SortKey> result = new LinkedList<SortKey>();
      final List<? extends SortKey> keys = sorter.getSortKeys();
      for (Iterator<? extends SortKey> it = keys.iterator(); it.hasNext();) {
        final SortKey sk = it.next();
        if (sk.getColumn() != 0) {
          result.add(sk);
        }
      }
      return result;
    }

    @Override
    public void setSortKeys(List<? extends SortKey> keys) {
      final LinkedList<SortKey> copy = new LinkedList<SortKey>(keys);
      copy.add(0, new SortKey(0, SortOrder.ASCENDING));
      sorter.setSortKeys(copy);
    }

    @Override
    public void toggleSortOrder(int column) {
      if (column != 0) {
        List<SortKey> keys = new ArrayList<SortKey>(sorter.getSortKeys());
        int sortIndex;
        for (sortIndex = keys.size() - 1; sortIndex >= 1; sortIndex--) {
          if (keys.get(sortIndex).getColumn() == column) {
            break;
          }
        }
        if (sortIndex == 0) {
          final SortKey sortKey = new SortKey(column, SortOrder.ASCENDING);
          keys.add(1, sortKey);
        } else if (sortIndex == 1) {
          final SortOrder order =
                keys.get(1).getSortOrder() == SortOrder.ASCENDING
                      ? SortOrder.DESCENDING
                      : SortOrder.ASCENDING;
          keys.set(1, new SortKey(column, order));
        } else {
          keys.remove(sortIndex);
          keys.add(1, new SortKey(column, SortOrder.ASCENDING));
        }

        sorter.setSortKeys(keys);
      }
    }

    // -----------------------------------------------------------------------
    // Event delegation from our controller to the sorter
    // -----------------------------------------------------------------------

    @Override
    public void allRowsChanged() {
      sorter.allRowsChanged();
    }

    @Override
    public void modelStructureChanged() {
      sorter.modelStructureChanged();
    }

    @Override
    public void rowsDeleted(int firstRow, int endRow) {
      sorter.rowsDeleted(firstRow, endRow);
    }

    @Override
    public void rowsInserted(int firstRow, int endRow) {
      sorter.rowsInserted(firstRow, endRow);
    }

    @Override
    public void rowsUpdated(int firstRow, int endRow) {
      sorter.rowsUpdated(firstRow, endRow);
    }

    @Override
    public void rowsUpdated(int firstRow, int endRow, int column) {
      sorter.rowsUpdated(firstRow, endRow, column);
    }

    // -----------------------------------------------------------------------
    // Event delegation from sorter to our listeners
    // -----------------------------------------------------------------------

    public void sorterChanged(RowSorterEvent e) {
      if (e.getType() == RowSorterEvent.Type.SORT_ORDER_CHANGED) {
        fireSortOrderChanged();
      } else if (e.getType() == RowSorterEvent.Type.SORTED) {
        final int[] indices = new int[e.getPreviousRowCount()];
        for (int i = 0; i < indices.length; i++) {
          indices[i] = e.convertPreviousRowIndexToModel(i);
        }
        fireRowSorterChanged(indices);
      }
    }
  }

  /**
   * Test main method.
   * 
   * @param args ignored
   */
  public static void main(String[] args) {
    final DefaultTableModel tm = new AccumulatingIntegerTableModel();
    for (int i = 0, j = 2 << i; i < 10; i++, j = 2 << i) {
      tm.addRow(new Object[] { Integer.valueOf(0), Integer.valueOf(i),
        Integer.valueOf(j), });
    }

    final JTable table = new JTable(tm);

    // use custom row sorter
    table.setAutoCreateRowSorter(false);
    table.setRowSorter(new CustomRowSorter(tm));

    // hide first column
    table.setAutoCreateColumnsFromModel(false);
    table.createDefaultColumnsFromModel();
    final TableColumnModel cm = table.getColumnModel();
    cm.removeColumn(cm.getColumn(0));

    final JPanel contentPane = new JPanel(new BorderLayout(6, 6));
    contentPane.add(new JScrollPane(table));

    final JFrame f = new JFrame("Test Frame: JTableSortingFun"); //$NON-NLS-1$
    f.setContentPane(contentPane);
    f.pack();
    f.setLocationRelativeTo(null);
    f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    f.setVisible(true);
  }
}
```
Das Beispiel benutzt ein Tabellenmodel das automatisch die letzte Zeile mit der Summe aller Werte der selben Spalte aktualisiert. Das Tabellenmodell hat eine Spalte 0 die für die Sortierung herangezogen wird. Außerdem gibt es einen RowSorter der wiederum einen anderen RowSorter die Arbeit erledigen lässt und selbst nur die SortKeys um einen ersten Eintrag erweitert, damit stets zuerst nach Spalte null sortiert wird. Darüber hinaus wird die Spalte 0 aus dem TableColumnModel entfernt. In etwa sollte das Deine Bedürfnisse decken.

Ebenius


----------



## Br4twurscht (6. Okt 2009)

Hallo Ebenius,

zunächst erstmal ein großes Danke für die Mühe. Is ja ganz schön groß geworden. Und beim ansehen is mir ne Idee gekommen, wie mans vielleicht einfacher lösen könnte. Wenns klappt werd ichs posten.

lg
Br4twurscht


----------



## Ebenius (7. Okt 2009)

Sind doch grad 150 Zeilen RowSorter. Mir gefällt die Variante ganz gut so, besonders weil sie wiederverwendbar ist. Mal sehen was Du dazu findest. 

Ebenius


----------



## Br4twurscht (7. Okt 2009)

Also meine Idee war gewesen, vor dem Sortieren die fest stehende Zeile zu entnehmen und nach dem Sortieren wieder einzufügen. Aber irgendwie bekomm ichs nich hin . Hier mal in Auszügen mein Lösungsansatz:

```
public class MyTableModel extends AbstractTableModel implements RowSorterListener {

private boolean sortiert = false;

...

public int getRowCount() {
		if (this.sortiert){
			return this.berechnung.getDieListe().size();
		}
		else
			return this.berechnung.getDieListe().size() + 1;

	}
}

...

@Override
	public void sorterChanged(RowSorterEvent e) {
		if (e.getType() == RowSorterEvent.Type.SORT_ORDER_CHANGED){
			this.sortiert = true;			
		}else{
			this.sortiert = false;
			this.fireTableRowsUpdated(this.berechnung.getDieListe().size() - 1, this.berechnung.getDieListe().size() - 1);
		}
	}
}
```

Also ich wende mein Flag sortiert nur bei getRowCount an, weil nur die letzte Zeile fest bleiben soll. Die letzte Zeile is nämlich, wie auch zufällig bei deinem Beispiel, ne Zeile, wo Daten aufsummiert und angezeigt werden.
Ich hab auch noch ne Menge mit dem feuern anderer Events probiert, aber bin auf keinen grünen Zweig gekommen. Ich kann halt anhand der Doku nicht nachvollziehen, wann was gezeichnet wird und wann genau das Event RowSorterEvent.Type.SORT_ORDER_CHANGED und RowSorterEvent.Type.SORTED kommt. Da find ich die Doku etwas ungenau. Aber es erschien mir schon so, als würde SORT_ORDER_CHANGED kommen, bevor sortiert wird und SORTED, wenns fertig is.


----------



## Br4twurscht (7. Okt 2009)

Oh, den ersten Fehler hab ich grad gefunden: Die -1 beim feuern muss jeweils weg. Da bekomm ich jetzt zwar ne ArrayIndexOutOfBoundsException und kann mir auch vorstellen warum, aber das müsste sich doch lösen lassen, hoff ich...


----------



## Ebenius (7. Okt 2009)

Nein, es ist kein Zufall. Ich habe darüber nachgedacht, was Du wohl machen willst und dachte mir, dass Du eine Summenzeile brauchst. 

Aber ich verstehe Dich jetzt nicht mehr. Du hast oben eine funktionierende, getestete, wiederverwendbare Lösung die Du geschenkt bekommst. Was genau bewegt Dich nun dazu, Zeilen hin und her sortieren zu wollen, wenn der Ansatz offensichtlich problematischer ist, als ein sauberer RowSorter?

Nachtrag: Was Du da tust ist höchst gefährlich und mit MVC nicht zu vereinbaren. Du verheiratest ein Modell mit einem RowSorter (View). Das rächt sich. Das Modell sollte niemals etwas vom Filtern und Sortieren der View mitbekommen. Ansonsten bekommst Du ganz lustige Effekte. 

Ebenius


----------



## Br4twurscht (7. Okt 2009)

Ebenius hat gesagt.:


> Aber ich verstehe Dich jetzt nicht mehr. Du hast oben eine funktionierende, getestete, wiederverwendbare Lösung die Du geschenkt bekommst. Was genau bewegt Dich nun dazu, Zeilen hin und her sortieren zu wollen, wenn der Ansatz offensichtlich problematischer ist, als ein sauberer RowSorter?


Irgendwie erscheint mir deine Lösung zu komplex und kompliziert. Und ehrlich gesagt, seh ich im Detail noch nich durch . Dein Ansatz is mir klar.



Ebenius hat gesagt.:


> Nachtrag: Was Du da tust ist höchst gefährlich und mit MVC nicht zu vereinbaren. Du verheiratest ein Modell mit einem RowSorter (View). Das rächt sich. Das Modell sollte niemals etwas vom Filtern und Sortieren der View mitbekommen. Ansonsten bekommst Du ganz lustige Effekte.


Ja, den Gedanken hatte ich auch schon, aber irgendwie fand ich die Idee toll 

Dann werd ich mir wohl doch nochmal deine Lösung zu Gemüte führen.


----------



## skorpionking (26. Apr 2011)

@Br4twurscht:
Hast du für das von dir beschriebene Problem (Sortierung ohne die letzte Zeile) eine einfachere Lösung finden können?
Falls ja, würde mich dein Ansatz sehr interessieren.

MFG skorpionking


----------



## Br4twurscht (26. Apr 2011)

Also ich vermute mal, dass der Vorschlag von Ebenius der sauberste ist. Allerdings war mir der Aufwand dann doch nicht Wert. Naja, und da ich sowieso Zeitprobleme hatte, hatte ich das Projekt, wo ich das nutzen wollte, sowieso erstmal auf Eis gelegt.

Also nein, ich habe keine einfache/kurze Lösung gefunden. Hatte aber auch nicht mehr danach gesucht und hätte die Zeile, die fest bleiben sollte dann wohl eher ausgegliedert in ein anderes Element.


----------

