english version

Swing


Wie kann ich eine Tabelle sortieren?

Die Aufgabe besteht wohl auch für Sie aus mehreren Teilen:

  1. Wie kann ich die Daten sortieren?
  2. Wie kann ich das Verhalten der JTable so anpassen, dass es erwartungskonform ist? Oder anders ausgedrückt - wie bekomme ich die Sortierung per Klick auf den Tabellenkopf?
  3. Wie füge ich die einzelnen Teile zusammen?

Wie kann ich die Daten sortieren?

Die Lösung hierfür ist einfach. Die Daten eine Tabelle werden üblicherweise in einer Collection vorgehalten. Die Klasse java.util.Collections stellt hierfür die Methode sort bereit. Unsere Aufgabe ist es lediglich das Interface java.util.Comparable zu implementieren.
Gehen wir jedoch Schritt für Schritt vor.

Ein einfaches Tabellenmodel für unsere Beispiele...

package de.bastie.sample;

import java.util.ArrayList;

import javax.swing.table.AbstractTableModel;


/**
 * Dies ist das einfache Beispiel Tabellenmodell.
 * @author Bastie - Sebastian Ritter
 * @version zuletzt getestet mit Java 1.5
 */
public class SimpleTabellenModel extends AbstractTableModel {

  /** Unsere Fachobjekte, die wir in der Tabelle darstellen wollen. */
  protected ArrayList<Person> fachObjekte = new ArrayList<Person> ();

  public SimpleTabellenModel () {
    this.loadData ();
  }

  /**
   * Hier sorgen wir dafür, dass wir auch Fachobjekte zur Verfügung haben.
   */
  protected void loadData () {
    fachObjekte.add (new Person ("Ritter", "Sebastian", "05.09.1975", "verheiratet"));
    fachObjekte.add (new Person ("Ritter", "Sebastián", "unbekannt", "ledig"));
    fachObjekte.add (new Person ("Knight", "Sebastian", "01.01.2222", "ledig"));
    fachObjekte.add (new Person ("Knight", "Sebastián", "05.09.1975", "ledig"));
  }

  private String [] kopfNamen = new String [] {"Nachname","Vorname","Geburtsdatum","Familienstand"};
  public int getAnzahlDerKopfNamen () {
    return this.kopfNamen.length;
  }

  public String getColumnName (final int spalte) {
    if (spalte < this.getAnzahlDerKopfNamen()) {
      return kopfNamen [spalte];
    }
    else {
      return super.getColumnName(spalte);
    }
  }

  public boolean isCellEditable(int rowIndex, int columnIndex) {
    return false;
  }

  public int getRowCount() {
    return this.fachObjekte.size();
  }

  public int getColumnCount() {
    return 4;
  }


  public Object getValueAt(final int zeile, final int spalte) {
    switch (spalte) {
    case 0 :
      return this.fachObjekte.get(zeile).getName ();
    case 1 :
      return this.fachObjekte.get(zeile).getVorname();
    case 2 :
      return this.fachObjekte.get(zeile).getFormatiertesGeburtsdatum();
    case 3 :
      return this.fachObjekte.get(zeile).getFamilienstand();
    default:
      return null;
    }
  }

}

Dazu gehören noch unsere Fachklassen - hier die Person:

package de.bastie.sample;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Logger;

/**
 * Beispiel BO
 * @author Bastie - Sebastian Ritter
 */
public class Person {
  private SimpleDateFormat sdf = new SimpleDateFormat ("dd.MM.yyyy");

  public Person (){}
  public Person (final String name, final String vorname,final String gebDatumFormat_tt_mm_jjjj,final String familienstand) {
    this.setName(name);
    this.setVorname(vorname);
    this.setGeburtsdatum(gebDatumFormat_tt_mm_jjjj);
    this.setFamilienstand(familienstand);
  }

  public String getFamilienstand() {
    return familienstand;
  }
  public void setFamilienstand(String familienstand) {
    this.familienstand = familienstand;
  }

  public Date getGeburtsdatum () {
    return this.geburtsdatum;
  }

  public String getFormatiertesGeburtsdatum() {
    return sdf.format(this.geburtsdatum);
  }
  public void setGeburtsdatum (final String tt_mm_jjjj) {
    try {
      this.geburtsdatum = sdf.parse(tt_mm_jjjj);
    }
    catch (final ParseException e) {
      Logger.global.throwing(this.getClass().getName() ,"setGeburtsdatum", e);
      this.geburtsdatum = new Date (0l);
    }
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public String getVorname() {
    return vorname;
  }
  public void setVorname(String vorname) {
    this.vorname = vorname;
  }
  private String name;
  private String vorname;
  private String familienstand;
  private Date geburtsdatum = new Date (0l);
}

Ein sortierbares Model

Wie bereits erwähnt müssen wir unser Model sortierbar machen. Zu diesem Zweck führen wir die Methode sort ein. Da wir nach den Eigenschaften der Person sortieren wollen brauchen wie dafür noch einen PersonComparator hier als innere Klasse realisiert.

package de.bastie.sample;

import java.util.Collections;
import java.util.Comparator;

/**
 * Ein Sortierbares Tabellenmodell für Personen
 * @author Bastie - Sebastian Ritter
 * @version zuletzt getestet mit Java 1.5
 */
public class SortierbaresTabellenModel extends SimpleTabellenModel {

  public void sort (final int spalte) {
    Collections.sort(this.fachObjekte, new PersonComparator (spalte));
  }

  private class PersonComparator implements Comparator {

    private final int spalte;

    public PersonComparator (final int spalte) {
      this.spalte = spalte;
    }

    public int compare(Object o1, Object o2) {
      if (o1 == null && o2 == null) {
        return 0;
      }
      else if (o1 == null) {
        return 1;
      }
      else if (o1 instanceof Person && o2 instanceof Person) {
        switch (this.spalte) {
        case 0 :
          return ((Person)o1).getName().compareTo(((Person)o2).getName());
        case 1 :
          return ((Person)o1).getVorname().compareTo(((Person)o2).getVorname());
        case 2 :
          return ((Person)o1).getGeburtsdatum().compareTo(((Person)o2).getGeburtsdatum());
        case 3 :
          return ((Person)o1).getFamilienstand().compareTo(((Person)o2).getFamilienstand());
        default :
          return 0;
        }
      }
      else {
        return 1;
      }
    }

  }

}

Das war es auch schon - wir können unsere Tabelle sortieren. Aufgabe 1 erledigt.
Ein Aufruf der Funktion könnte zum Beispiel über einen ActionListener wie diesen erfolgen.

package de.bastie.sample;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JTable;

/**
 * Sortieren einer JTable mit dem sortierbaren Tabellenmodell.
 * @author Bastie - Sebastian Ritter
 * @version zuletzt getestet mit Java 1.5
*/
public class SortTableActionListener implements ActionListener {

  private final JTable tabelle;

  public SortTableActionListener(final JTable tabelle) {
    this.tabelle = tabelle;
  }

  public void actionPerformed(ActionEvent e) {
    if (tabelle.getModel() instanceof SortierbaresTabellenModel) {
      ((SortierbaresTabellenModel)tabelle.getModel()).sort(Integer.parseInt(e.getActionCommand()));
    }
  }

}

Wie kann ich das Verhalten der JTable so anpassen, dass es erwartungskonform ist?

...oder anders ausgedrückt - wie bekomme ich die Sortierung per Klick auf den Tabellenkopf? Das ganze ist etwas schwerer. Leider können wir der JTable nicht einfach einen Listener hierfür anhängen. Im folgenden Stelle ich vier Klassen vor, welche das Problem "universell" lösen. Hierbei handelt es sich um eine Erweiterung von Sun Klassen, um die Möglichkeit einen ActionListener an den Tabellenkopf zu hängen. Wenn Sie diese Klassen verwenden besteht Ihre Aufgabe lediglich in dem registrieren eines ActionListeners und Implementieren eines Comparators für Ihre Fachobjekte. Die vier vorgestellten Klassen verhalten sich ansonsten wie eine normale Swing JTable.

Wir erweitern die JTable um unseren Tabellenkopf und unser SpaltenModell zu registrieren.

package de.bastie.swing.tabelle;

import java.util.logging.Logger;

import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;

/**
 * Erweiterung der JTable um nützlich Funktionen.
 * @author Bastie - Sebastian Ritter
 * @version last tested with Java 1.5
 */
public class BstJTable extends JTable {

  public BstJTable () {
    this (null, null);
  }

  public BstJTable (final TableModel model) {
    this (model, null);
  }

  /**
   * Do not use this Constructor because the TableColumnModel
   * is replacing.
   * @param model model that represents the business
   * @param columnModel is ignored
   * @param selectionModel how can users selected
   * @deprecated TableColumnModel is ignored. Better you use one of the simplifier constructors.
   */
  public BstJTable (final TableModel model,
                    final TableColumnModel columnModel,
                    final ListSelectionModel selectionModel) {
    this (model, selectionModel);
    Logger.getLogger(this.getClass().getName()).warning("TableColumnModel ignored - used constructor ist deprecated");
  }

  /**
   * Construct a new JTable with a selectable table header.
   * 
All other constructors calls him. * @param model model that represents the business * @param selectionModel how can users selected */ public BstJTable (final TableModel model, final ListSelectionModel selectionModel) { super (model, null, selectionModel); // Own ColumnModel this.setColumnModel(new BstTableColumnModel()); // Own TableHeader this.setTableHeader(new BstJTableHeader()); this.getTableHeader().setColumnModel(this.getColumnModel()); } public BstJTableHeader getTableHeader () { return (BstJTableHeader) this.tableHeader; } }

In unserem Model für unsere Tabellenspalten legen wir lediglich unseren Darsteller (Renderer) fest. Dies wäre zwar grds. nicht notwendig aber ein bisschen schön soll es ja auch aussehen.

package de.bastie.swing.tabelle;

import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.TableColumn;

/**
 * TableColumnModel to react from selection a table header.
 * @author Bastie - Sebastian Ritter
 */
public class BstTableColumnModel extends DefaultTableColumnModel {

  public void addColumn (final TableColumn aColumn) {
    super.addColumn(aColumn);
    this.addMyHeaderRenderer (aColumn);
  }

  /**
   * @param column
   */
  private void addMyHeaderRenderer(final TableColumn column) {
    column.setHeaderRenderer(new BstSelectionTableCellRenderer ());
  }
}

Der Darsteller selbst erzeugt lediglich für jeden Spaltennamen ein JLable Objekt und formatiert diese je nachdem, ob es gerader mit der Maus angeklickt wurde.

package de.bastie.swing.tabelle;

import java.awt.Component;

import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.table.TableCellRenderer;

/**
 * TableCellRenderer for select a column header.
 * @author Bastie - Sebastian Ritter
 * @version last tested with Java 1.5
 */
public class BstSelectionTableCellRenderer implements TableCellRenderer {

  private int pressedColumn = -1;

  public Component getTableCellRendererComponent (final JTable table,
                                                  final Object value,
                                                  final boolean isSelected,
                                                  final boolean hasFocus,
                                                  final int row,
                                                  final int column) {
    JLabel b = new JLabel ((value == null) ? "" : value.toString());
    // Dann stell das ganze mal richtig dar...
    if (column == this.pressedColumn) {
      b.setBackground(UIManager.getColor("control"));
      b.setBorder(BorderFactory.createEtchedBorder(UIManager.getColor("controlHighlight"),UIManager.getColor("controlShadow")));
    }
    else {
      b.setBackground(UIManager.getColor("controlShadow"));
      b.setBorder(BorderFactory.createEtchedBorder(UIManager.getColor("controlLtHighlight"),UIManager.getColor("controlDkShadow")));
    }
    return b;
  }

  public void setPressedColumn (final int col) {
    this.pressedColumn = col;
  }

}

Wenden wir uns nun der Klasse BstJTableHeader zu. Diese macht im wesentlich folgendes:

package de.bastie.swing.tabelle;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;

import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumnModel;

/**
 * TableHeader for action on selection.
 * @author Bastie - Sebastian Ritter
 * @version last tested with Java 1.5
 */
public class BstJTableHeader extends JTableHeader implements MouseListener {

  public BstJTableHeader () {
    this (null);
  }
  public BstJTableHeader (final TableColumnModel columnModel) {
    super (columnModel);
    this.addMouseListener(this);
  }

  private ArrayList listener = new ArrayList ();

  public void addActionListener (final ActionListener listener) {
    this.listener.add(listener);
  }

  public void removeActionListener (final ActionListener listener) {
    this.listener.remove(listener);
  }

  /**
   * Fire a ActionEvent with the table model column as
   * command.
   * @param column
   * @param when
   * @param modifiers
   */
  protected void fireActionEvent (final int column,
                                  final long when,
                                  final int modifiers) {
    final ActionEvent e = new ActionEvent (this,
                                     ActionEvent.ACTION_PERFORMED,
                                     ""+this.getTable()
                                            .getColumnModel()
                                            .getColumn(column)
                                            .getModelIndex(),
                                     when,
                                     modifiers);
    this.fireActionEvent(e);
  }
  protected void fireActionEvent (final ActionEvent e) {
    for (int i = 0; i < listener.size(); i++) {
      listener.get(i).actionPerformed(e);
    }
  }

  /**
   * Method calls fireActionEvent with this table column of view.
   * @param e MouseEvent
   */
  public void mouseClicked (final MouseEvent e) {
    this.fireActionEvent(this.columnAtPoint(e.getPoint()),e.getWhen(),e.getModifiers());
  }

  public void mousePressed(MouseEvent e) {
    JTableHeader header = (JTableHeader) e.getSource();
    int column = header.columnAtPoint(e.getPoint());
    TableCellRenderer cr = header.getTable()
                                 .getColumnModel()
                                 .getColumn(column)
                                 .getHeaderRenderer();
    if (cr instanceof BstSelectionTableCellRenderer) {
      ((BstSelectionTableCellRenderer) cr).setPressedColumn(column);
      header.repaint();
    }
  }

  public void mouseReleased(MouseEvent e) {
    JTableHeader header = (JTableHeader) e.getSource();
    int column = header.columnAtPoint(e.getPoint());
    TableCellRenderer cr = header.getTable().getColumnModel().getColumn(column).getHeaderRenderer();
    if (cr instanceof BstSelectionTableCellRenderer) {
      ((BstSelectionTableCellRenderer) cr).setPressedColumn(-1);
      header.repaint();
    }
  }

  public void mouseEntered(MouseEvent e) {}

  public void mouseExited(MouseEvent e) {}

}

Für den Test ist natürlich noch eine kleine Anwendung notwendig. Das Ergebnis sehen Sie am Ende...

package de.bastie.sample;

import java.awt.BorderLayout;

import javax.swing.JApplet;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;

import de.bastie.swing.tabelle.BstJTable;

/**
 * Dieses Beispiel verdeutlicht, wie du auf einen Mausklick
 * auf den Tabellenkopf reagieren kannst.
 * @author Bastie - Sebastian Ritter
 * @version zuletzt getestet mit Java 1.5
 */
public class BeispielKlickbarerTabellenKopf extends JApplet {

  private JTable tabelle;

  /**
   * Initialisierung unserer Anwendung / unseres Applets
   */
  public void init () {
    // Wir initialisieren unsere Ansicht ...
    BstJTable tabelle = new BstJTable ();

    tabelle.getTableHeader().addActionListener(new SortTableActionListener (tabelle));

    tabelle.setModel (new SortierbaresTabellenModel());
    this.getContentPane().add (new JScrollPane (tabelle));
  }

  /**
   * Die Startmethode initialisiert die Anwendung für das Stand-Alone-Beispiel.
   * @param args
   */
  public static void main (final String [] args) {
    JFrame fenster = new JFrame ("Beispiel klickbarer Tabbellenkopf");
    fenster.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    fenster.setSize(400,300);
    BeispielKlickbarerTabellenKopf beispiel = new BeispielKlickbarerTabellenKopf ();
    beispiel.init();
    fenster.getContentPane().add(beispiel, BorderLayout.CENTER);
    fenster.setVisible(true);
    beispiel.start();
  }
}
Java Archiv mit Quellen und Klassen.
all rights reserved © Bastie - Sebastian Ritter @: w³: http://www.Bastie.de
Diese Seite ist Bestandteil der Internetpräsenz unter http://www.Bastie.de


Java Cobol Software Resourcen Service Links Über mich