JTable - Teil 4 - Darstellung der Daten

Status
Nicht offen für weitere Antworten.
B

Beni

Gast
JTable - Teil 4 - Darstellung der Daten

Bereits in im vorhergehenden Teil hat man gesehen, dass das JTable offenbar mehr kann als nur Text zu zeichnen:

beni-albums-jtable-picture56-table-04.png


1. Das Standardverhalten
Mit Hilfe der Methode TableModel#getColumnClass versucht das JTable herauszufinden, wie diese Column am besten dargestellt werden kann.

Ein Blick in den Quellcode von JTable verrät, dass Number, Float, Double, Date, Icon, ImageIcon und Boolean eine Spezialbehandlung erhalten.

Sollte ein Object nicht dazu passen, wird die "toString" Methode benutzt.

Demonstrieren wir mal, wie das aussieht:
Java:
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.util.Date;

import javax.swing.Icon;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;

public class JTableDemo{
   public static void main( String[] args ){
      // Unser TableModel (siehe unten)
      Model model = new Model();
      
      // Das JTable initialisieren
      JTable table = new JTable( model );
      
      JFrame frame = new JFrame( "Demo" );
      frame.getContentPane().add( new JScrollPane( table ) );
      
      frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
      frame.pack();
      frame.setVisible( true );
   }
}

Ein einfaches Icon, damit man nicht Bilder laden, etc... muss
Java:
class DemoIcon implements Icon{
	public int getIconHeight() {
		return 15;
	}
	public int getIconWidth() {
		return 15;
	}
	public void paintIcon(Component c, Graphics g, int x, int y) {
		g.setColor( Color.DARK_GRAY );
		g.fillOval( x, y, 10, 10 );
		g.setColor( Color.GREEN );
		g.fillOval( x+5, y+5, 10, 10 );
	}
}

Wiedereinmal ein TableModel. Diesmal wird AbstractTableModel als Parent-Klasse benutzt, denn so muss man weniger Methoden implementieren
Java:
class Model extends AbstractTableModel{
	private Object[] objects;
	private Class[] classes;
	
   	public Model(){
   		objects = new Object[]{
				"Text",
				Float.valueOf( 5.123f ),
				Boolean.valueOf( true ),
				new Date( System.currentTimeMillis() ),
				new DemoIcon(),
				Color.RED
   		};
   		
   		classes = new Class[]{
				String.class,
				Float.class,
				Boolean.class,
				Date.class,
				Icon.class,
				Color.class
   		};
   	}
   
   	// Die Anzahl Columns
   	public int getColumnCount() {
   		return objects.length;
   	}
   
   	// Die Anzahl Rows
   	public int getRowCount() {
   		return 1;
   	}
   
   	// Die Titel der einzelnen Columns (Der Name der Klasse die Dargestellt wird)
   	public String getColumnName(int column) {
   		return getColumnClass( column ).getName();
   	}
   
   	// Der Wert der Zelle (rowIndex, columnIndex)
   	public Object getValueAt(int rowIndex, int columnIndex) {
   		return objects[ columnIndex ];
   	}

   	// Eine Angabe, welchen Typ von Objekten in den Columns angezeigt werden soll
   	public Class getColumnClass(int columnIndex) {
   		return classes[columnIndex];
   	}
}

beni-albums-jtable-picture59-table-07.png


Wie man sieht, wurde das Color-Objekt nicht gerade schön dargestellt. Da das JTable nicht wusste, wie es mit einem Color-Objekt umgehen soll, wurde einfach die "toString"-Methode benutzt.

2. Ein einfacher TableCellRenderer
Wie kann man dem JTable also nun sagen, dass Color auch speziell angezeigt werden soll?
Indem man einen TableCellRenderer implementiert, und dem JTable über die Methode JTable#setDefaultRenderer übergibt.

Der Einfachheit halber, wird für das Beispiel ein DefaultTableCellRenderer genommen, und ein bisschen verändert.

Die bekannte Tabelle mit einem zusätzlichen Befehl
Java:
public class JTableDemo{
   public static void main( String[] args ){
      // Unser TableModel (siehe unten)
      Model model = new Model();
      
      // Das JTable initialisieren
      JTable table = new JTable( model );
      
      // Hier wird der JTable gesagt, dass Objekte des Types "Color" vom "ColorTableCellRenderer" dargestellt werden
      table.setDefaultRenderer( Color.class, new ColorTableCellRenderer() );
      
      JFrame frame = new JFrame( "Demo" );
      frame.getContentPane().add( new JScrollPane( table ) );
      
      frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
      frame.pack();
      frame.setVisible( true );
   }
}

Das DemoIcon bleibt unverändert
Java:
class DemoIcon implements Icon{
   ...
}

Auch an "Model" muss nichts verändert werden
Java:
class Model extends AbstractTableModel{
   ...
}

Und jetzt kommt der neue TableCellRenderer.
Java:
class ColorTableCellRenderer extends DefaultTableCellRenderer{
	public Component getTableCellRendererComponent(JTable table, Object value,
			boolean isSelected, boolean hasFocus, int row, int column) {
		super.getTableCellRendererComponent(table, value, isSelected,
				hasFocus, row, column);
		
		Color color = (Color)value;
		
		setText( color.getRed() + ", " + color.getBlue() + ", " + color.getGreen() );
		setForeground( color );
		
		return this;
	}
}

beni-albums-jtable-picture60-table-08.png


Das JTable erwartet von einem TableCellRenderer eine Component, welche genau eine Zelle darstellt. Allerdings wird diese Component nicht für immer behalten, sondern sie muss nur kurz diese Zelle zeichnen, und wird danach nicht mehr benötigt.

Was macht "ColorTableCellRenderer"?
Sobald das JTable vom ColorTableCellRenderer eine Componen haben möchte, ruft es ersteinmal die entsprechende Methode von DefaultTableCellRenderer auf. Dort werden Hintergrundfarbe und Schriftart gesetzt.
Danach setzt ColorTableCellRenderer einen Text der zu der Farbe passt (die RGB-Werte), und verändert die Schriftfarbe.
Und dann gibt er sich selbst zurück, denn betrachtet man die API, so sieht man, dass DefaultTableCellRenderereine Unterklasse von JLabel (und damit auch von Component) ist, und dies deshalb ohne Probleme geht.

Wichtig ist: es muss nie eine neue Component hergestellt werden, das Schlüsselwort new hat hier nichts verloren.

3. Alles zum gleichen TableCellRenderer
Manchmal möchte man vielleicht mehr Kontrolle über die Art, wie die Zellen dargestellt werden.
Eine einfache Variante ist folgende: Das TableModel gibt für alle ColumnClasses an, sie seien vom Typ Object. Und für den Typ Object setzt man einen eigenen TableCellRenderer.

Die Tabelle mit anderem TableCellRenderer
Java:
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.util.Date;

import javax.swing.Icon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;

public class JTableDemo{
   public static void main( String[] args ){
      // Unser TableModel (siehe unten)
      Model model = new Model();
      
      // Das JTable initialisieren
      JTable table = new JTable( model );

      // Den Spezial-CellRenderer übergeben.
      table.setDefaultRenderer( Object.class, new Renderer() );
            
      JFrame frame = new JFrame( "Demo" );
      frame.getContentPane().add( new JScrollPane( table ) );
      
      frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
      frame.pack();

      frame.setVisible( true );
   }
}

Das DemoIcon bekommt noch die Möglichkeit, andere Farben zu benutzen
Java:
class DemoIcon implements Icon{
	private Color top, bottom;
	
	public DemoIcon( Color top, Color bottom ){
		this.top = top;
		this.bottom = bottom;
	}
	
	public int getIconHeight() {
		return 15;
	}
	public int getIconWidth() {
		return 15;
	}
	public void paintIcon(Component c, Graphics g, int x, int y) {
		g.setColor( top );
		g.fillOval( x, y, 10, 10 );
		g.setColor( bottom );
		g.fillOval( x+5, y+5, 10, 10 );
	}
}

Das TableModel gibt nun für jede Column an, dass Object's angezeigt werden
Java:
class Model extends AbstractTableModel{
	private Object[][] objects;
	
   	public Model(){
   		objects = new Object[][]{
   				{"Text",
				new Float( 5.123f ),
				new Boolean( true ),
				new Date( System.currentTimeMillis() ),
				new DemoIcon( Color.DARK_GRAY, Color.GREEN ),
				Color.RED},
				
   				{"Hallo",
				new Double( 531.45 ),
				new Boolean( false ),
				new Date( System.currentTimeMillis() + 50000000 ),
				new DemoIcon( Color.BLUE, Color.GRAY ),
				Color.CYAN},
				
	   			{"Tschau",
				new Integer( 123 ),
				new Boolean( true ),
				"unbekannt",
				new DemoIcon( Color.LIGHT_GRAY, Color.MAGENTA ),
				Color.PINK}	
   		};
   	}
   
   	// Die Anzahl Columns
   	public int getColumnCount() {
   		return 6;
   	}
   
   	// Die Anzahl Rows
   	public int getRowCount() {
   		return 3;
   	}
   
   	// Die Titel der einzelnen Columns
   	public String getColumnName(int column) {
   		return String.valueOf( column );
   	}
   
   	// Der Wert der Zelle (rowIndex, columnIndex)
   	public Object getValueAt(int rowIndex, int columnIndex) {
   		return objects[ rowIndex ][ columnIndex ];
   	}

   	// Eine Angabe, welchen Typ von Objekten in den Columns angezeigt werden soll
   	public Class getColumnClass(int columnIndex) {
   		return Object.class;
   	}
}

Der neue TableCellRenderer entscheidet für jede Zelle einzeln, wie sie dargestellt werden soll.
Java:
class Renderer extends JLabel implements TableCellRenderer{
	private Color colorSelected = new Color( 200, 255, 200 );
	private Color colorFocus = new Color( 255, 200, 200 );
	private Color colorNormal = new Color( 200, 200, 255 );

	public Renderer(){
		setOpaque( true );
	}
	
	public Component getTableCellRendererComponent(JTable table, Object value,
			boolean isSelected, boolean hasFocus, int row, int column) {
		
		// die normalen Farben
		setForeground( Color.BLACK );
		if( hasFocus )
			setBackground( colorFocus );
		else if( isSelected )
			setBackground( colorSelected );
		else
			setBackground( colorNormal );
		
		setText( null );
		setIcon( null );
		
		if( value instanceof Date )
			setText( ((Date)value).toGMTString() );
		else if( value instanceof Icon )
			setIcon( (Icon)value );
		else if( value instanceof Color ){
			Color color = (Color)value;
			setForeground( color );
			setText( color.getRed() + ", " + color.getGreen() + ", " + color.getBlue() );
		}
		else if( value instanceof Boolean ){
			if( ((Boolean)value).booleanValue() )
				setText( "yes" );
			else
				setText( "no" );
		}
		else
			setText( value.toString() );
		
		return this;
	}
}

beni-albums-jtable-picture61-table-09.png


Man beachte: in Column 3, ganz unten, ist ein String, und kein Date-Object. Da der Renderer für jede Zelle einzeln entscheiden kann, gibt es hier keine Probleme.[/quote]
Andre_Uhres hat gesagt.:
4. Darstellung auf Zeilen- oder Tabellen-Ebene

Normalerweise werden Renderer für eine bestimmte Datenklasse oder Datenspalte geschrieben.

Aber wie kann man Zeilen-abhängige oder Tabellen-abhängige Erfordnisse angehen?
Z.B. wie kann man:

a) Zeilen abwechselnd in einer Tabelle färben
b) Den Rand der selektierten Zelle ändern


Am einfachsten geht das indem man die Methode prepareRenderer() der Klasse JTable überschreibt:

Java:
import java.awt.*;
import java.text.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.table.*;
public class TableRowRendering extends JFrame{
    public TableRowRendering(){
        //  Model:
        Object[] columnNames = {"Type", "Date", "Company", "Shares", "Price"};
        Object[][] data =
        {
            {"Buy", new Date(), "IBM", new Integer(1000), new Double(80.50)},
            {"Sell",new Date(), "MicroSoft", new Integer(2000), new Double(6.25)},
            {"Sell",new Date(), "Apple", new Integer(3000), new Double(7.35)},
            {"Buy", new Date(), "Nortel", new Integer(4000), new Double(20.00)}
        };
        DefaultTableModel model = new DefaultTableModel(data, columnNames) {
            public Class getColumnClass(int column) {
                return getValueAt(0, column).getClass();
            }
        };
        // prepareRenderer überschreiben:
        table = new JTable( model ) {
            public Component prepareRenderer(
                    TableCellRenderer renderer, int row, int column) {
                Component c = super.prepareRenderer(renderer, row, column);
                if (!isRowSelected(row)) {
                    String type = (String)getModel().getValueAt(row, 0);
                    c.setBackground(row % 2 == 0 ? null : Color.LIGHT_GRAY );
                }
                if (isRowSelected(row) && isColumnSelected(column))
                    ((JComponent)c).setBorder(selected);
                return c;
            }
        };
        table.setPreferredScrollableViewportSize(table.getPreferredSize());
        getContentPane().add(new JScrollPane( table ), BorderLayout.CENTER);
    }
    private JTable table;
    private Border selected = new LineBorder(Color.GREEN);
    public static void main(String[] args) {
        TableRowRendering frame = new TableRowRendering();
        frame.setDefaultCloseOperation( EXIT_ON_CLOSE );
        frame.pack();
        frame.setLocationRelativeTo( null );
        frame.setVisible(true);
    }
}
Beni hat gesagt.:
5. Wichtige Bemerkung
Man sollte niemals einen Code schreiben, der so aussieht:

Java:
public class Renderer implements TableCellRenderer {

	[...]

	public Component getTableCellRendererComponent(JTable table, Object value, 
		boolean isSelected, boolean hasFocus, int row, int column) {
      
		JLabel label = new JLabel();  // FALSCH

		[...]

		return label;
	}
}

Wieso nicht? Weil man immer wieder dieselbe Component verwenden kann. Denn das JTable ruft die getTableCellRendererComponent-Methode auf, und ruft gleich anschliessend den paint-Befehl der erhaltenen Component auf. Erst danach wird die "nächste" Component geholt.

Es ist wie das Tragen von Wasser: entweder läuft man mit einem Eimer zum Fluss, wieder zurück und wirft denn Eimer dann fort (weil er ja gebraucht ist), kauft sich dann einen neuen; oder man läuft mit demselben Eimer zurück zum Fluss. Ziemlich offensichtlich, dass die zweite Variante die bessere ist.

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

Neue Themen


Oben