JTable - Anwendungen - Teil 2 - Das sortierte JTable

Status
Nicht offen für weitere Antworten.
B

Beni

Gast
Beni hat gesagt.:
JTable - Anwendungen 2 - Das sortierte JTable
(Der Source ist für Java 1.5 bestimmt, bekannte Probleme sind am Ende des Dokumentes aufgelistet)

Wer Java 1.6 benutzen kann, sollte die neuen Features der JTable nutzen: Teil 8 dieses Tutorials zeigt, wie der TableRowSorter benutzt werden kann. Der TableRowSorter ist wesentlich bequemer, und auch einfacher zu verstehen, als der Code in diesem Teil "Anwendung 2"

Oft sieht man Tabellen, in welchem man die Columns sortieren kann.

Selbstverständlich ist das auch mit dem JTable möglich, es bedeutet allerdings einigen Zusatzaufwand.
Deshalb soll hier auch ein Beispiel aufgeführt sein, wie man das implementieren kann.

Der Grundgedanke dabei ist: zwischen dem TableModel und dem JTable wird ein weiteres TableModel geschaltet, welches das Original-Model sortiert der JTable übergibt (sozusagen ein ßbersetzer).

Das System, für Klassen TableCellRenderer / Editoren anzumelden wird übernommen, allerdings für Comparatoren.

Grundsätzlich ist dieses SortedTable so geschrieben, dass auch ganz normale TableModels sortiert werden können.

Ein neuer Listener, der SortetTableListener, kann dem SortedTable hinzugefügt werden, um Veränderungen in der Sortierung zu erfahren.

Standardmässig verwendet der CellRenderer der den Header zeichnet einen solchen Listener um herauszufinden, welche Column wie sortiert ist.

Das ist natürlich nur ein Weg, eine sortierte JTable herzustellen, andere Lösungen sind denkbar. Diese Variante ist vorallem für "Gelegenheitssortierer" praktisch, also für diejenigen die eine sortierte Tabelle haben möchten, ohne viel zusätzlichen Code zu schreiben.

Zum Ressourcenverbrauch dieser Variante: Der Sotieralgorithmus hat eine Laufzeit von O(n log n), was die schnellstmögliche Implementation einer Sortierung ist.
Ressourcen gehen vorallem durch einen zusätzlichen Array (der die Reihenfloge der sortierten Zeilen speichert) verloren (der Overhead ist also O(n)). Allerdings garantiert dieser Array, dass das SortedTable praktisch gleichschnell gezeichnet wird, wie eine normale JTable (Das ist wichtig, falls der Benutzer scrollen will). Gerade wenn eine Tabelle viele Zeilen (>10'000) angzeigt, und noch weitere hinzukommen, kann die Geschwindigkeit eines Programmes aber (deutlich) verschlechtert werden.

Das Interface für ein erweitertes TableModel. Es muss nicht verwendet werden, gibt aber dem Programmierer bessere Kontrolle.

Java:
import javax.swing.table.TableModel;

/**
 * Ein erweitertes TableModel
 */
public interface SortableTableModel extends TableModel {
	/**
	 * Gibt an, ob die Rows anhand dieser Column sortierbar sind.
	 * @param column Die Column
	 * @return true, falls die Daten sortierbar sind
	 */
	public boolean isColumnSortable( int column );
}

Ein Interface für Listener, welche über Veränderungen bei der Sortierung informiert werden möchten.
Java:
/**
 * @author Benjamin Sigg
 * @version 1.0
 */
public interface SortedTableListener {
	/**
	 * Wird aufgerufen, falls eine Column neu sortiert wurde.
	 * @param table Das Table, in welchem das Ereigniss stattfand
	 * @param column Die column, im Koordinatensystem des TableModels
	 * @param ascending Die Richtung, true = ascending, false = descending
	 */
	public void sorted( SortedTable table, int column, boolean ascending );
	
	/**
	 * Wird aufgerufen, falls keine Column mehr sortiert ist.
	 * @param table Das Table, in welchem das Ereigniss stattfand
	 */
	public void desorted( SortedTable table );
}

Dieser CellRenderer gibt an, welche Column sortiert ist, indem er einen kleinen Pfeil zeichnet.
Java:
import java.awt.Color;
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;

import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.table.TableCellRenderer;

/**
 * Ein einfacher Header-Renderer für das SortedJTable
 * 
 * @author Benjamin Sigg
 * @version 1.0
 */
public class DefaultSortTableHeaderRenderer extends JPanel
		implements SortedTableListener, TableCellRenderer{
	
	
	private TableCellRenderer renderer;
	
	private int column = -1;
	private boolean ascending = false;
	
	private Component image;
	
	private Icon ascendingIcon, descendingIcon;
	private Icon icon;
	
	/**
	 * Konstruktor des Renderers
	 * @param renderer Der Originalrenderer, welcher dargestellt, und evtl. um
	 * einen Pfeil erweitert wird.
	 */
	public DefaultSortTableHeaderRenderer( TableCellRenderer renderer ){
		this.renderer = renderer;
		
		ascendingIcon = createAscendingIcon();
		descendingIcon = createDescendingIcon();
		
		setLayout( null );
		
		setOpaque( false );
	}
	
	/**
	 * Eine Implementation des DefaultSortTableHeaderRenderers, der
	 * das Interface {@link javax.swing.plaf.UIResource UIResrouce}
	 * implementiert, und somit durch LookAndFeel's ersetzt werden kann.
	 * @author Benjamin Sigg
	 */
	public static class UIResource extends DefaultSortTableHeaderRenderer 
		implements javax.swing.plaf.UIResource{
		
		public UIResource( TableCellRenderer renderer ){
			super( renderer );
		}
	}
	
	public Component getTableCellRendererComponent(JTable table,Object value,boolean isSelected,boolean hasFocus,int row,int column){
		image = renderer.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column );
		
		removeAll();
		add( image );
		
		/**
		 * Workaround
		 * Eigentlich sollte man diese Einstellung irgendwie aus dem LookAndFeel
		 * ziehen können.
		 */
		if( image instanceof JComponent )
			((JComponent)image).setOpaque( false );
		
		if( this.column == table.convertColumnIndexToModel( column )){
			if( ascending )
				icon = ascendingIcon;
			else
				icon = descendingIcon;
		}
		else
			icon = null;
		
		return this;
	}
	
	public void paint( Graphics g ){
		image.paint( g );
		if( icon != null ){
			int iw = icon.getIconWidth();
			int ih = icon.getIconHeight();
			
			int w = getWidth();
			int h = getHeight();
			
			Insets insets = new Insets( 2, 2, 2, 2 );
			
			if( image instanceof Container ){
				insets = ((Container)image).getInsets();
			}
			
			if( getComponentOrientation() == ComponentOrientation.LEFT_TO_RIGHT )
				icon.paintIcon( this, g, w - iw - insets.right, (h - ih)/2 );
			else if( getComponentOrientation() == ComponentOrientation.RIGHT_TO_LEFT )
				icon.paintIcon( this, g, insets.left, (h - ih)/2 );
			else
				icon.paintIcon( this, g, w - iw - insets.right, (h - ih)/2 );
				
		}
	}
	
	public void setBounds(int x,int y,int w,int h){
		super.setBounds( x, y, w, h );
		image.setBounds( 0, 0, w, h );
	}
	
	public void update( Graphics g ) {
		image.update( g );
	}
	
	public Dimension getMinimumSize() {
		return image.getMinimumSize();
	}
	
	public Dimension getMaximumSize() {
		return image.getMaximumSize();
	}
	
	public Dimension getPreferredSize() {
		return image.getPreferredSize();
	}
	
	public void desorted( SortedTable table ) {
		column = -1;
	}
	
	public void sorted( SortedTable table, int column, boolean ascending) {
		this.column = column;
		this.ascending = ascending;
	}
	
	protected Icon createAscendingIcon(){
		return new Icon(){
			public int getIconHeight() {
				return 3;
			}
			
			public int getIconWidth() {
				return 5;
			}
			
			public void paintIcon(Component c, Graphics g, int x, int y) {
				g.setColor( Color.BLACK );
				g.drawLine( x, y+2, x+4, y+2 );
				g.drawLine( x+1, y+1, x+3, y+1 );
				g.drawLine( x+2, y, x+2, y );
			}
		};
	}
	protected Icon createDescendingIcon(){
		return new Icon(){
			public int getIconHeight() {
				return 3;
			}
			
			public int getIconWidth() {
				return 5;
			}
			
			public void paintIcon(Component c, Graphics g, int x, int y) {
				g.setColor( Color.BLACK );
				g.drawLine( x, y, x+4, y );
				g.drawLine( x+1, y+1, x+3, y+1 );
				g.drawLine( x+2, y+2, x+2, y+2 );
			}
		};		
	}
}
..
 
B

Beni

Gast
Beni hat gesagt.:
Das sortierte JTable. Alle wichtigen Zusatzklassen sind als private innere Klasse definiert. Von aussen ist dem JTable nicht viel anzusehen.
Java:
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.text.Collator;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.Vector;

import javax.swing.JTable;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableModel;

/**
 * Ein JTable, das die Einträge nach den Columns sortieren kann.
 * 


 * Wichtig: um eine Column zu sortieren muss ein entsprechender Comparator
 * installiert sein [b]und[/b] das TableModel muss für die Column die 
 * richtig Class liefern.
 * 


 * Die Methode "getSelectedRow" bezieht sich auf das sortierte Model, nicht
 * auf die Originalanordnung. Es kann aber die Methode {@link #getOriginalSelectedRow()}
 * benutzt werden, welche sich auf das unsortierte Model bezieht.
 * 
 * @author Benjamin Sigg
 * @version 1.0
 */

public class SortedTable extends JTable{
	/** Das TableModel mit den Originaldaten */
	private TableModel model;

	/** Ein TableModel das zwischengeschaltet wird, um die Sortierung vorzunehmen */
	private Model sortModel;

	private Listener listener = new Listener();

	private Vector<SortedTableListener> listeners = new Vector<SortedTableListener>();

	private Hashtable<Class<?>, Comparator<?>> comparators = new Hashtable<Class<?>, Comparator<?>>();

	private boolean reactionResort = true;

	private boolean doNotAlterSortingDirection = true;
	
	/**
	 * Standardkonstruktor
	 */
	public SortedTable(){
		sortModel = new Model();
		JTableHeader header = getTableHeader();
		
		header.addMouseListener( listener );
		
		TableCellRenderer renderer = header.getDefaultRenderer();
		if( renderer instanceof SortedTableListener )
			addSortedTableListener( (SortedTableListener)renderer );

		createDefaultComparators();
		super.setModel( sortModel );
	}

	/**
	 * Konstruktor mit Model mit Daten.
	 * @param model Das TableModel, welches Daten enthält.
	 */
	public SortedTable(TableModel model){
		this();
		setModel( model );
	}

	/**
	 * Konstruktor mit Model mit Daten.
	 * @param model Das TableModel, welches Daten enthält.
	 */
	public SortedTable(SortableTableModel model){
		this();
		setModel( model );
	}

	/**
	 * Setzt das TableModel mit den Daten.
	 * @param model Das Model
	 */
	public void setModel( TableModel model ) {
		if( model == null )
			throw new IllegalArgumentException( "Model must not be null" );

		if( sortModel == null )
			super.setModel( model );
		else{
			TableModel oldModel = this.model;
			this.model = model;
			sortModel.modelChange( oldModel, model );
		}
	}

	/**
	 * Gibt das Model zurück, welches die Daten liefert.
	 * @return Das Model
	 */
	public TableModel getOriginalModel() {
		return model;
	}

	/**
	 * Gibt den Index der selektierten Rows, im Koordinatensystem des
	 * Original-Models zurück.
	 * @return Der Index
	 * 
	 */
	public int getOriginalSelectedRow() {
		int row = getSelectedRow();
		if( row != -1 )
			row = sortModel.convertSortToOriginal( row );

		return row;
	}

	/**
	 * Gibt die Indices der selektierten Rows zurück.
	 * @return Die Indices im Koordinatensystem des Original-Models
	 */
	public int[] getOriginalSelectedRows() {
		int[] rows = getSelectedRows();

		for( int i = 0, n = rows.length; i < n; i++ )
			rows[i] = sortModel.convertSortToOriginal( rows[i] );

		return rows;
	}

	/**
	 * Fügt die angegeben Rows des Original-Models der Selektion hinzu.
	 * @param index0 Der erste Index, inklusive
	 * @param index1 Der zweite Index, inklusive
	 */
	public void addOriginalRowSelectionIntervall( int index0, int index1 ) {
		int min = Math.min( index0, index1 );
		int max = Math.max( index0, index1 );

		for( int i = min; i <= max; i++ ){
			int index = sortModel.convertSortToOriginal( i );
			addRowSelectionInterval( index, index );
		}
	}

	/**
	 * Gibt an, ob Standardmässig neu sortiert werden soll, falls das Model
	 * Veränderungen meldet. Ist dieser Wert falsch, kann das die Sortierung bei
	 * jeder Veränderung des Models verloren gehen. Die ursprüngliche Sortierung
	 * des Original-Models wird verwendet.
	 * @param resort true, falls neu Sortiert werden soll, andernfalls false
	 */
	public void setResortOnModelChange( boolean resort ) {
		reactionResort = resort;
	}

	/**
	 * Setzt den Comparator für diese spezielle Klasse.
	 * @param clazz Die Klasse
	 * @param comparator Der zu benutzende Comparator
	 */
	public void setDefaultComparator( Class<?> clazz, Comparator comparator ) {
		comparators.put( clazz, comparator );
	}

	/**
	 * Gibt den Comparator der angegebenen Column zurück.
	 * @param column Index der Column
	 * @return Der Comparator
	 */
	public Comparator getComparator( int column ) {
		return getComparator( model.getColumnClass( column ) );
	}

	/**
	 * Gibt den Comparator für eine Klasse zurück.
	 * @param clazz Die Klasse
	 * @return Der Comparator
	 */
	public Comparator getComparator( Class clazz ) {
		Comparator comp = internalGetComparator( clazz );
		if( comp == null ){
			Class[] interfaces = clazz.getInterfaces();
			Class comparable = Comparable.class;
			for( int i = 0, n = interfaces.length; i < n; i++ ){
				if( interfaces[i].equals( comparable ) )
					return internalGetComparator( comparable );
			}
		}

		return comp;
	}

	private Comparator internalGetComparator( Class clazz ) {
		if( clazz != null ){
			Object value = comparators.get( clazz );
			if( value == null ){
				return internalGetComparator( clazz.getSuperclass() );
			}
			else
				return (Comparator)value;
		}
		else
			return null;
	}

	/**
	 * Wird diese Option aktiviert, so wird bei dem Klick auf ein Header
	 * die Sortierrichtung nur verändert, wenn dieser Header bereits
	 * sortiert ist. 
	 * @param doNotAlterSortingDirection <code>true</code> wenn die 
	 * Sortierrichtung möglichst nicht verändert werden soll.
	 */
	public void setDoNotAlterSortingDirection(
			boolean doNotAlterSortingDirection ) {
		this.doNotAlterSortingDirection = doNotAlterSortingDirection;
	}
	
	/**
	 * Gibt an, ob die Sortierrichtung nicht verändert werden soll.
	 * @return <code>true</code> wenn die Richtung möglichst nicht
	 * verändert werden soll.
	 * @see #setResortOnModelChange(boolean)
	 */
	public boolean isDoNotAlterSortingDirection() {
		return doNotAlterSortingDirection;
	}
	
	/**
	 * Gibt an, ob diese Column sortierbar ist. Sollte ein SortableTableModel
	 * verwendet werden, wird dieses Model gefragt, andernfalls wird ein
	 * Comparator zur columnClass gesucht, und falls einer gefunden wird, wird
	 * true zurückgegeben.
	 * @param column Der Index der Column, im TableModel-System
	 * @return true, falls die Column sortierbar ist
	 */
	public boolean isColumnSortable( int column ) {
		if( model != null ){
			if( model instanceof SortableTableModel )
				return ((SortableTableModel)model).isColumnSortable( column );
			else
				return getComparator( model.getColumnClass( column ) ) != null;
		}
		else
			return false;
	}

	/**
	 * Setzt den JTableHeader.
	 * @param tableHeader der Header
	 */
	public void setTableHeader( JTableHeader tableHeader ) {
		if( tableHeader == null )
			throw new IllegalArgumentException( "Header must not be null" );

		JTableHeader oldHeader = getTableHeader();
		if( oldHeader != null ){
			oldHeader.removeMouseListener( listener );
			TableCellRenderer renderer = oldHeader.getDefaultRenderer();
			if( renderer instanceof SortedTableListener ){
				removeSortedTableListener( (SortedTableListener)renderer );
			}
		}
		
		TableCellRenderer renderer = tableHeader.getDefaultRenderer();
		if( renderer instanceof SortedTableListener ){
			removeSortedTableListener( (SortedTableListener)renderer );
		}
		
		super.setTableHeader( tableHeader );
		tableHeader.addMouseListener( listener );
	}

	/**
	 * Fügt einen SortedTableListener hinzu.
	 * @param listener Der Listener
	 */
	public void addSortedTableListener( SortedTableListener listener ) {
		if( listeners != null )
			listeners.add( listener );
	}

	/**
	 * Entfernt einen SortedTableListener.
	 * @param listener Der Listener
	 */
	public void removeSortedTableListener( SortedTableListener listener ) {
		if( listeners != null )
			listeners.remove( listener );
	}

	/**
	 * Verschickt eine Nachricht an alle Listeners, dass das Table sortiert
	 * wurde.
	 * @param column Die Column
	 * @param ascending true, falls aufsteigend sortiert wurde
	 */
	protected void fireSorted( int column, boolean ascending ) {
		for( int i = 0, n = listeners.size(); i < n; i++ )
			listeners.get( i ).sorted( this, column, ascending );

		sortModel.fireTableDataChanged();
		getTableHeader().repaint();
	}

	/**
	 * Verschickt eine Nachricht an alle Listeners, dass das Table nicht mehr
	 * sortiert ist.
	 */
	protected void fireDesorted() {
		for( int i = 0, n = listeners.size(); i < n; i++ )
			listeners.get( i ).desorted( this );

		getTableHeader().repaint();
	}

	/**
	 * Hebt die Anzeige der Sortierung auf, und es gibt keine keine Reaktion,
	 * falls etwas neues hinzugefügt wird.
	 */
	public void desort() {
		sortModel.desort();
	}

	/**
	 * Setzt alle Standardcomperatoren.
	 */

	protected void createDefaultComparators() {
		setDefaultComparator( String.class, Collator.getInstance() );
		setDefaultComparator( Comparable.class, new ComparableComparator() );
	}

	/**
	 * Stellt das normale JTableHeader her. Dieser Header benutzt ein
	 * {@link DefaultSortTableHeaderRenderer}.

	 * Wann immer diesem Header ein anderer Renderer gesetzt wird, wird dieser
	 * Renderer in ein DefaultSortTableHeaderRenderer verpackt.
	 */
	protected JTableHeader createDefaultTableHeader() {
		JTableHeader header = new JTableHeader( super.getColumnModel() ){
			public void setDefaultRenderer( TableCellRenderer defaultRenderer ) {
				TableCellRenderer old = getDefaultRenderer();
				if( old instanceof SortedTableListener && SortedTable.this != null )
					removeSortedTableListener( (SortedTableListener)old );
				
				DefaultSortTableHeaderRenderer renderer =
					new DefaultSortTableHeaderRenderer.UIResource( defaultRenderer );
				
				if( SortedTable.this != null )
					addSortedTableListener( renderer );

				super.setDefaultRenderer( renderer );
			}
		};

		header.setColumnModel( this.getColumnModel() );
		return header;
	}

	/**
	 * Sortiert den Inhalt des Tables nach der angegebenen Column.
	 * @param column Der Index der zu sortierenden Column.
	 * @param ascendent True, falls aufsteigend sortiert werden soll, für
	 *            absteigendes Sortieren false
	 */
	public void sort( int column, boolean ascendent ) {
		sortModel.sort( column, ascendent );
	}

	/**
	 * Dieser MouseListener wird dem JTableHeader hinzugefügt, um die zu
	 * sortierende Column herauszufinden.
	 */
	private class Listener extends MouseAdapter{
		public Listener(){

		}

		public void mouseClicked( MouseEvent e ) {
			JTableHeader header = (JTableHeader)e.getSource();
			int column = convertColumnIndexToModel( header.columnAtPoint( e
					.getPoint() ) );

			// System.out.println( column + " " + convertColumnIndexToModel(
			// column )
			// + " " + convertColumnIndexToView( column ));

			if( column >= 0 && isColumnSortable( column ) ){
				sortModel.sort( column );
			}
		}
	}

	/**
	 * Dieses Model steht zwischen dem tatsächlichen Model, und der JTable. Es
	 * sorgt für die Sortierung, indem es Rows vertauscht zurückgibt.
	 */
	private class Model extends AbstractTableModel implements
			TableModelListener{
		private Row[] rows = new Row[0];

		private int rowCount = 0;

		private int column = -1;

		private boolean ascendent = false;

		/**
		 * Standardkonstruktor
		 */
		public Model(){
		}

		/**
		 * Sortiert die angegebene Column.
		 * @param column Der Index der Column
		 */
		public void sort( int column ) {
			if( column == this.column ){
				ascendent = !ascendent;

				for( int i = 0, j = rowCount - 1; i < j; i++, j-- ){
					Row temp = rows[i];
					rows[i] = rows[j];
					rows[j] = temp;
				}

				fireTableDataChanged();
				fireSorted( column, ascendent );
			}
			else{
				if( doNotAlterSortingDirection )
					sort( column, ascendent );
				else
					sort( column, true );
			}
		}

		/**
		 * Sortiert die aktuelle Column neu.
		 */
		private void resort() {
			sort( column, ascendent );
		}

		/**
		 * Sortiert die angegebene Column.
		 * @param column Der Index der Column
		 * @param ascendent true, falls aufsteigend sortiert werden soll, false
		 *            sofern absteigend sortiert werden soll.
		 */
		public synchronized void sort( int column, boolean ascendent ) {
			this.column = column;
			this.ascendent = ascendent;

			if( rowCount > 1 ){
				Arrays.sort( rows, 0, rowCount, new RowComparator() );
				fireTableChanged( new TableModelEvent( this, 0, rowCount - 1,
						TableModelEvent.ALL_COLUMNS, TableModelEvent.UPDATE ) );
			}
			fireSorted( column, ascendent );
		}

		/**
		 * Hebt die Sortierung auf. Alle Rows bleiben an ihrer aktuellen
		 * Position und werden nicht mehr weiterbewegt.
		 */
		public void desort() {
			column = -1;
			fireDesorted();
		}

		/**
		 * Wird aufgerufen, falls das TableModel mit den Daten ausgetauscht
		 * wurde.
		 * @param oldModel Das alte Model
		 * @param newModel Das neue Model
		 */
		public synchronized void modelChange( TableModel oldModel,
				TableModel newModel ) {
			if( oldModel != null )
				oldModel.removeTableModelListener( this );
			newModel.addTableModelListener( this );

			ensureAllInArray( newModel );

			fireTableStructureChanged();
			desort();
		}

		/**
		 * ßberprüft und versichert, dass alle Rows des Models gespeichert sind.
		 * @param model Das Model, dessen Rows gespeichert sein sollen.
		 */
		private void ensureAllInArray( TableModel model ) {
			int size = model.getRowCount();
			ensureRowArray( size );

			for( int i = 0; i < size; i++ ){
				if( rows[i] == null )
					rows[i] = new Row( i );
				else
					rows[i].setIndex( i );
			}

			rowCount = size;
		}

		/**
		 * Versichert, dass der Row-Array genug lang für <code>capacity</code>
		 * viele Elemente ist.
		 * @param capacity Die minimale Länge des Arrays
		 */
		private void ensureRowArray( int capacity ) {
			if( rows.length < capacity ){
				Row[] array = new Row[Math.max( capacity, rows.length * 2 ) + 1];
				System.arraycopy( rows, 0, array, 0, rowCount );
				rows = array;
			}
		}

		/**
		 * Wandelt den Row-Index des Original-Models in einen Row-Index dieses
		 * Models um.
		 * @param row Der originale Index
		 * @return Der echte Index
		 */
		public int convertSortToOriginal( int row ) {
			return rows[row].getIndex();
		}

		/**
		 * Wandelt den aktuellen Row-Index in den Row-Index des Originals um.
		 * @param row Der echte Index
		 * @return Der originale Index
		 */
		public int convertOriginalToSort( int row ) {
			for( int i = 0; i < rowCount; i++ )
				if( rows[i].getIndex() == row )
					return i;

			return -1;
		}

		public Class<?> getColumnClass( int columnIndex ) {
			return model.getColumnClass( columnIndex );
		}

		public int getColumnCount() {
			if( model == null )
				return 0;
			else
				return model.getColumnCount();
		}

		public String getColumnName( int columnIndex ) {
			return model.getColumnName( columnIndex );
		}

		public int getRowCount() {
			if( model == null )
				return 0;
			else
				return model.getRowCount();
		}

		public Object getValueAt( int rowIndex, int columnIndex ) {
			return model.getValueAt( rows[rowIndex].getIndex(), columnIndex );
		}

		public boolean isCellEditable( int rowIndex, int columnIndex ) {
			return model
					.isCellEditable( rows[rowIndex].getIndex(), columnIndex );
		}

		public void setValueAt( Object aValue, int rowIndex, int columnIndex ) {
			model.setValueAt( aValue, rows[rowIndex].getIndex(), columnIndex );
		}

		/**
		 * Wird aufgerufen, falls das Original-Model verändert wurde.
		 * @param e Das Event
		 */
		public void tableChanged( TableModelEvent e ) {
			int columns = model.getColumnCount();

			if( e.getFirstRow() == TableModelEvent.HEADER_ROW ){
				ensureAllInArray( model );
				fireTableStructureChanged();
			}
			else if( e.getType() != TableModelEvent.UPDATE || rowCount != model.getRowCount()  ){
				int delta =  model.getRowCount() - rowCount;
				int oldRows = rowCount;
				ensureAllInArray( model );				
				
				if( delta > 0 )
					fireTableRowsInserted( oldRows, model.getRowCount()-1 );
				else if( delta < 0 )
					fireTableRowsDeleted( model.getRowCount(), oldRows-1 );
			}

			if( reactionResort && column >= 0 && column < columns )
				resort();

			else if( rowCount > 0 ){
				if( column != -1 ){
					desort();
				}
				fireTableRowsUpdated( 0, rowCount-1 );
			}
		}

		/**
		 * Eine "Wrapper"-Klasse für Rows. Jede Row enthält einen Index zu einer
		 * Original-Row.
		 */
		private class Row{
			private int index;

			public Row(int index){
				this.index = index;
			}

			public int getIndex() {
				return index;
			}

			public void setIndex( int index ) {
				this.index = index;
			}
		}

		/**
		 * Vergleicht zwei Rows. Dabei wird wann immer möglich eine Reihenfolge
		 * erstellt.
		 */
		private class RowComparator implements Comparator{
			private int columns;

			private Comparator[] comparators;

			public RowComparator(){
				columns = model.getColumnCount();
				comparators = new Comparator[columns];
			}

			public int compare( Object o1, Object o2 ) {

				Row a = (Row)o1;
				Row b = (Row)o2;

				if( comparators[column] == null )
					comparators[column] = getComparator( column );

				int result = compare( comparators[column], a, b, column );

				if( result == 0 ){
					int index = 0;

					while( index < columns && result == 0 ){
						if( index != column ){
							if( isColumnSortable( index ) ){
								if( comparators[index] == null )
									comparators[index] = getComparator( index );

								result = compare( comparators[index], a, b,
										index );
							}
						}
						index++;
					}
				}

				return result;
			}

			private int compare( Comparator comparator, Row a, Row b, int column ) {
				if( ascendent )
					return comparator.compare( model.getValueAt( a.getIndex(),
							column ), model.getValueAt( b.getIndex(), column ) );
				else
					return comparator.compare( model.getValueAt( b.getIndex(),
							column ), model.getValueAt( a.getIndex(), column ) );
			}
		}
	}

	/**
	 * Standardcomparator der zwei Comparables vergleicht, indem er ihre
	 * <code>compareTo</code> Methoden benutzt.
	 */
	private class ComparableComparator implements Comparator{
		public int compare( Object o1, Object o2 ) {
			return ((Comparable)o1).compareTo( o2 );
		}
	}
}
..
 
B

Beni

Gast
Beni hat gesagt.:
Beispiel zur Verwendung
Jetzt noch ein Beispiel, wie das SortedTable benutzt werden kann.
Dabei wird ein beinahe normales DefaultTableModel verwendet, welches allerdings als Klassenangabe "Double" verwendet.
Diese Tabelle ist editierbar.


Java:
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Vector;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;

public class JTableDemo{
   public static void main( String[] args ){
      // Die Namen der Columns
      String[] titles = new String[]{ "A", "B", "C", "D" };
      
      // Das Model das wir verwenden werden. Hier setzten wir gleich die
      // Titel, aber es ist später immer noch möglich weitere Columns oder
      // Rows hinzuzufügen.
      final DefaultTableModel model = new DefaultTableModel( titles, 0 ){
		public Class getColumnClass(int columnIndex) {
			return Double.class;
		}
      };
      
      // Das JTable initialisieren
      JTable table = new SortedTable( model );
      ((SortedTable)table).setResortOnModelChange( false );
      
      // Buttons, damit das alles schöner aussieht.
      final JButton buttonAddRow = new JButton( "add row" );
      final JButton buttonRemRow = new JButton( "remove row" );
      final JButton buttonAddCol = new JButton( "add column" );
      
      buttonRemRow.setEnabled( false );
      
      // Den Buttons ein paar Reaktionen geben
      buttonAddRow.addActionListener( new ActionListener(){
         public void actionPerformed(ActionEvent e) {
            // Die Anzahl Columns (Breite) der Tabelle
            int size = model.getColumnCount();
            
            // einen neuen Vector mit Daten herstellen
            Vector newDatas = createDataVector( "row", size );
            
            // eine neue Row hinzufügen
            model.addRow( newDatas );
            
            
            // das Entfernen erlauben
            buttonRemRow.setEnabled( true );
         }
      });
      
      buttonAddCol.addActionListener( new ActionListener(){
         public void actionPerformed(ActionEvent e) {
            int size = model.getRowCount();
            Vector newDatas = createDataVector( "column", size );
            String name = String.valueOf( model.getColumnCount() );
            model.addColumn( name, newDatas );
         }
      });
      
      buttonRemRow.addActionListener( new ActionListener(){
         public void actionPerformed(ActionEvent e) {
            int size = model.getRowCount();
            int index = (int)(Math.random() * size);
            model.removeRow( index );
            
            buttonRemRow.setEnabled( size > 1 );
         }
      });
      
      JFrame frame = new JFrame( "Demo" );
      
      Container content = frame.getContentPane();
      
      content.add( new JScrollPane( table ), BorderLayout.CENTER );
      content.add( buttonAddRow, BorderLayout.NORTH );
      content.add( buttonRemRow, BorderLayout.SOUTH );
      content.add( buttonAddCol, BorderLayout.WEST );
      
      frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
      frame.pack();
      frame.setVisible( true );
   }
   
   public static Vector createDataVector( String prefix, int size ){
      Vector vector = new Vector( size );
      for( int i = 0; i < size; i++ )
         vector.add( new Double( Math.random() ) );
      
      return vector;
   }
}

Das Resultat
beni-albums-jtable-picture64-table-12.png


Ich hab dieses Model ziemlich schnell zusammengeschrieben, es ist möglich, dass da noch Bugs vorhanden sind.
Wer einen findet, möge sich doch bei mir melden!

Probleme mit SortedTable
  • Normarweise sind Objekte nicht sortierbar, deshalb verweigert das Table auch die Sortierung bei normalen Objekten.
    Es muss entweder ein Comparator für Objekte gesetzt werden, oder die Methode getColumnClass des TableModels muss eine Klasse zurückgeben, für die ein Comparator installiert ist.
  • Die Methode getSelectedRow bezieht sich auf das tatsächlich angezeigte, und nicht auf das TableModel.
    Um die Zeile zu finden, welche im TableModel selektiert ist, muss die Methode getOriginalSelectedRow verwendet werden.

History
16.02.2005:
- Icons wurden verkehrt herum angezeigt, ist korrigiert (THX STEagleEye).

23.03.2005:
- Der Renderer wurde umgeschrieben, so dass nun die verschiedenen LookAndFeels besser unterstützt werden.
- Der JTableHeader der bei "createDefaulTableHeader" generiert wird, wurde umgeschrieben. Er verpackt nun jeden Renderer in den DefaultSortTableHeaderRenderer, und verwaltet die Renderer, welche "SortTableListener" implementiert haben.
- Der Konstruktor von SortedTable leicht abgeändert, es wird nun kein Renderer mehr initialisiert, da dies automatisch durch den Default-TableHeader geschieht.

4.2.2006:
- Sollte die Tabelle nicht sortiert sein, und die Anzahl Zeilen verändert werden, so wird jetzt ein TableModelEvent verschickt, welches das Neuzeichnen garantiert.

© August 2004
Dieses Tutorial unterliegt dem Copyright, Kopien (auch nur von Teilen) sind nur für nicht-komerzielle Zwecke gestattet; Detailfragen (insbesondere bei Unsicherheiten)
 
Status
Nicht offen für weitere Antworten.

Oben