JTable (Java API): Unterschied zwischen den Versionen
K (→Weiterführende Artikel) |
K (→Darstellung auf Zeilen- oder Tabellen-Ebene: prepareRenderer) |
||
Zeile 416: | Zeile 416: | ||
</code=java> | </code=java> | ||
− | === Darstellung auf Zeilen- oder Tabellen-Ebene: prepareRenderer === | + | ==== Darstellung auf Zeilen- oder Tabellen-Ebene: prepareRenderer ==== |
Normalerweise werden Renderer für eine bestimmte Datenklasse oder Datenspalte geschrieben. | Normalerweise werden Renderer für eine bestimmte Datenklasse oder Datenspalte geschrieben. |
Version vom 8. August 2008, 21:27 Uhr
Die JTable ist eine Klasse der graphischen Oberfläche Swing. Die JTable stellt eine Tabelle dar, also ein Gitter von Zellen, wobei jede Zelle mit Text, Bildern, o.ä. gefüllt sein kann.
Inhaltsverzeichnis
Aufbau der JTable
Helfer
Die JTable benutzt verschiedene Helfer-Klassen und -Interfaces, jeder dieser Helfer übernimmt eine spezielle Aufgabe. Die folgende Liste ist eine (unvollständige) Aufzählung der Helfer und ihrer Aufgabe.
- Das TableModel liefert die Einträge für die Zellen. Die JTable kann das Model jederzeit nach dem Inhalt einer bestimmten Zelle fragen, und das Model hat den entsprechenden Wert zu nennen. Wichtig ist, dass das Model nichts mit der Darstellung der Daten zu tun hat.
- Die TableCellRenderer sind für die Darstellung einzelner Zellen zuständig. Die JTable fragt jeweils einen Renderer nach einer Component, welche eine Zelle zeichnen wird.
- Der TableCellEditor wird von der JTable benutzt, um einzelne Zellen zu editieren.
- Der JTableHeader stellt die Titel über den einzelnen Spalten dar.
- Das TableColumnModel speichert die Position einzelner Spalten, und kann auch einzelne Spalten umherschieben.
- Die TableColumn stellt eine einzelne Spalte dar. Eine TableColumn kann benutzt werden, um zusätzliche Informationen zu einer Spalte (z.B. minimale Breite) zu speichern.
- Der RowSorter wird benutzt, um die Zeilen der Tabelle gemäss dem Inhalt einer Spalte zu sortieren.
Kommunikation
Für die Kommunikation zwischen der JTable und ihrer Helfer, werden oft Observer eingesetzt. Oft verwendet werden:
- Der TableModelListener wird einem TableModel hinzugefügt. Ändern sich die Werte des Models, wird durch den Listener die JTable informiert.
- Der TableColumnListener wird dem TableColumnModel hinzugefügt. Das TableColumnModel kann die JTable über neue, entfernte oder verschobene Spalten informieren.
Die JTable benutzen
Daten - TableModel
Das JTable lässt sich auf verschiedene Arten mit Daten füllen.
Array
Eine sehr einfache Art und Weise ist ein 2-dimensionaler Array. Dieser Array wird der JTable direkt im Konstruktor übergeben. Ein Array von Strings gibt die Titel der Spalten an. Die JTable wird die Methode toString() benutzen, um die Objekte in dem Array in Text umzuwandeln. <code=java> import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTable;
public class JTableDemo{
public static void main( String[] args ){ // Die Daten für die JTable Object[][] data = new Object[][]{ {"a", "b", "c", "d"}, {"e", "f", "g", "h"}, {"i", "j", "k", "l"} }; // Die Titel der Spalten String[] title = new String[]{ "A", "B", "C", "D" }; // Das JTable initialisieren JTable table = new JTable( data, title ); JFrame frame = new JFrame( "Demo" ); frame.getContentPane().add( new JScrollPane( table ) ); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.pack(); frame.setVisible( true ); }
} </code=java>
DefaultTableModel
Sehr einfach kann man mit einem DefaultTableModel veränderliche Daten in die JTable einbringen. Wann immer man Daten ändern will, ruft man einfach die entsprechenden Methoden des DefaultTableModels auf (addRow, setValueAt, ...). <code=java> import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Random;
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 ){ // Das Model herstellen final DefaultTableModel model = new DefaultTableModel(); // Die Titel der Spalten setzen model.setColumnIdentifiers( new Object[]{ "A", "B", "C", "D" }); // Das Model mit zufälligen Daten befüllen Random random = new Random(); for( int r = 0; r < 10; r++ ){ Object[] row = new Object[ model.getColumnCount() ]; for( int c = 0; c < row.length; c++ ){ row[c] = random.nextInt( 9 ) + 1; } model.addRow( row ); } // Ein Knopf herstellen, der jeden Wert in der JTable um 1 erhöht. JButton plus = new JButton( "+1" ); plus.addActionListener( new ActionListener(){ public void actionPerformed( ActionEvent e ) { int rows = model.getRowCount(); int columns = model.getColumnCount(); for( int r = 0; r < rows; r++ ){ for( int c = 0; c < columns; c++ ){ // Wert aus dem Model lesen, und neu schreiben int value = (Integer)model.getValueAt( r, c ); model.setValueAt( value+1, r, c ); } } } }); // Die JTable initialisieren JTable table = new JTable( model ); JFrame frame = new JFrame( "Demo" ); frame.add( new JScrollPane( table ), BorderLayout.CENTER ); frame.add( plus, BorderLayout.SOUTH ); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.pack(); frame.setVisible( true ); }
} </code=java>
Eigenes TableModel
Ein eigenes TableModel ist praktisch, wenn man Daten nicht im vornherein auf Spalten aufteilen möchte, sondern erst in dem Augenblick, indem wirklich die einzelnen Spalten abgefragt werden.
Das TableModel bekommt einige TableModelListener zugewiesen. Diese Observer müssen benachrichtigt werden, wenn der Inhalt des Models verändert wird.
Das folgende Beispiel zeigt ein TableModel welches die Attribute einer Flasche (Bottle) auf je einer Zeile verteilt.
Zuerst die Definition einer Flasche: <code=java>// Eine Klasse deren Inhalt jeweils in einer Zeile // dargestellt wird public class Bottle{
private String name; private boolean full; private int size;
public Bottle( String name, int size, boolean full ) { this.name = name; this.full = full; this.size = size; }
public boolean isFull() { return full; } public void setFull( boolean full ) { this.full = full; } public String getName() { return name; } public void setName( String name ) { this.name = name; } public int getSize() { return size; } public void setSize( int size ) { this.size = size; }
}</code=java>
Nun das TableModel welches Flaschen verarbeiten kann: <code=java>// Ein Model das Bottle-Objekte aufnimmt, und pro Zeile jeweils // ein Bottle-Objekt ausliest. public class BottleModel implements TableModel{
// Die Observer dieses Models private List<TableModelListener> listeners = new ArrayList<TableModelListener>(); // Der Daten dieses Models private List<Bottle> bottles = new ArrayList<Bottle>();
// fügt einen Observer hinzu public void addTableModelListener( TableModelListener l ) { listeners.add( l ); }
// entfernt einen Observer public void removeTableModelListener( TableModelListener l ) { listeners.remove( l ); } // fügt eine neue Flasche ein. public void addBottle( Bottle bottle ){ int row = bottles.size(); bottles.add( bottle );
// alle Observer von der neuen Flasche unterrichten TableModelEvent event = new TableModelEvent( this, row, row, TableModelEvent.ALL_COLUMNS, TableModelEvent.INSERT ); for( TableModelListener listener : listeners ) listener.tableChanged( event ); }
// gibt die Anzahl Spalten der Tabelle an public int getColumnCount() { return 3; } // gibt die Anzahl Zeilen der Tabelle an public int getRowCount() { return bottles.size(); } // gibt den Titel einer Spalte an public String getColumnName( int columnIndex ) { switch( columnIndex ){ case 0: return "Name"; case 1: return "Grösse"; case 2: return "Gefüllt"; default: throw new IllegalArgumentException( "Wrong column" ); } } // sagt der JTable, welche Zellen editiert werden dürfen public boolean isCellEditable( int rowIndex, int columnIndex ) { return true; } // gibt den Wert einer Zelle an public Object getValueAt( int rowIndex, int columnIndex ) { Bottle bottle = bottles.get( rowIndex ); switch( columnIndex ){ case 0: return bottle.getName(); case 1: return Integer.valueOf( bottle.getSize() ); case 2: return Boolean.valueOf( bottle.isFull() ); default: throw new IllegalArgumentException( "Wrong column" ); } }
// wird vom JTable aufgerufen, um den Wert einer Zelle zu ersetzen public void setValueAt( Object aValue, int rowIndex, int columnIndex ) { // Der Wert wird zuerst gespeichert... // Die Casts funktioniert nur deshalb, weil die Methode // "getColumnClass" den jeweils passenden Typ ausgibt! Bottle bottle = bottles.get( rowIndex ); switch( columnIndex ){ case 0: bottle.setName( (String)aValue ); break; case 1: bottle.setSize( ((Integer)aValue).intValue() ); break; case 2: bottle.setFull( ((Boolean)aValue).booleanValue() ); break; default: throw new IllegalArgumentException( "Wrong column" ); }
// ... dann werden alle Observer von dem neuen Wert unterrichtet TableModelEvent event = new TableModelEvent( this, rowIndex, rowIndex, columnIndex, TableModelEvent.UPDATE ); for( TableModelListener listener : listeners ) listener.tableChanged( event ); } // Gibt an, welcher Typ Objekte in einer Spalte angezeigt werden. // Die JTable wählt je nach Typ eine andere Darstellungsart public Class<?> getColumnClass( int columnIndex ) { switch( columnIndex ){ case 0: return String.class; case 1: return Integer.class; case 2: return Boolean.class; default: throw new IllegalArgumentException( "Wrong column" ); } }
}</code=java>
Und nun eine Klasse die das alles sichtbar macht: <code=java>public class JTableDemo{
public static void main( String[] args ){ // Ein neues Model erzeugen BottleModel model = new BottleModel();
// Das Model mit Daten füllen model.addBottle( new Bottle( "Whiskey", 3, true )); model.addBottle( new Bottle( "Mineral", 10, false )); model.addBottle( new Bottle( "Bier", 5, false )); model.addBottle( new Bottle( "Sirup", 7, true )); model.addBottle( new Bottle( "Kaffee", 1, true ));
// 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 ); }
}</code=java>
Darstellung - TableCellRenderer
Um einzelne Zellen zu zeichnen, benutzt die JTable einen TableCellRenderer. Für verschiedene Spalten können verschiedene TableCellRenderer eingesetzt werden.
Die Wahl eines TableCellRenderers kann spalten- oder typabhängig geschehen.
Im ersten Fall sucht man die TableColumn einer Spalte, und benutzt die Methode setCellRenderer um der Spalte einen Renderer zuzuweisen
Im zweiten Fall benutzt man die Methode setDefaultRenderer des JTables um einen Renderer mit einem bestimmten Typ zu assoziieren. Die JTable wird dann das TableModel nach dem Typ einer Spalte fragen (über die Methode getColumnClass), und entsprechend einen Renderer wählen.
Das folgende Beispiel zeigt eine Tabelle die einige Farben auflistet. Zuerst die benötigten Imports: <code=java>import java.awt.Color; import java.awt.Component; import java.awt.Graphics;
import javax.swing.Icon; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableCellRenderer; </code=java>
Nun folgt der TableCellRenderer der Color-Objekte verarbeitet. <code=java> // Ein Renderer der Color-Objekte darstellt public class ColorRenderer extends JLabel implements TableCellRenderer{ public Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column ){ // "value" ist der Wert, der gezeigt werden soll Color color = (Color)value;
// Position des Icons, und Transparenz festlegen setHorizontalAlignment( CENTER ); setOpaque( true );
// Vorder- und Hintergrundfarbe der Zelle festlegen if( isSelected ){ setBackground( table.getSelectionBackground() ); setForeground( table.getSelectionForeground() ); } else{ setBackground( table.getBackground() ); setForeground( table.getForeground() ); }
// Die Farbe als gefüllter Kreis zeigen setIcon( new ColorIcon( color ));
// Dem Aufrufer "this" als Component die gezeichnet wird, angeben. return this; }
// Ein Icon das eine Farbe als Kreis zeichnet. private class ColorIcon implements Icon{ private Color color;
public ColorIcon( Color color ){ this.color = color; }
public int getIconHeight(){ return 10; }
public int getIconWidth(){ return 10; }
public void paintIcon( Component c, Graphics g, int x, int y ){ g.setColor( color ); g.fillOval( x, y, 10, 10 );
g.setColor( c.getForeground() ); g.drawOval( x, y, 10, 10 ); } } }</code=java>
Schlussendlich wird alles miteinander in Verbindung gebracht: <code=java> public class JTableDemo{
public static void main( String[] args ){ // Die Column-Titles String[] title = new String[]{ "Name", "Color" }; // Das Model, welches für jede Spalte den Typ des Inhaltes angibt DefaultTableModel model = new DefaultTableModel( title, 0 ){ @Override public Class<?> getColumnClass( int columnIndex ){ switch( columnIndex ){ case 0: return String.class; case 1: return Color.class; default: return null; } } }; // Das Model mit einigen Werten befüllen model.addRow( new Object[]{ "White", Color.WHITE } ); model.addRow( new Object[]{ "Black", Color.BLACK } ); model.addRow( new Object[]{ "Red", Color.RED } ); model.addRow( new Object[]{ "Orange", Color.ORANGE } ); model.addRow( new Object[]{ "Yellow", Color.YELLOW } ); model.addRow( new Object[]{ "Green", Color.GREEN } ); model.addRow( new Object[]{ "Cyan", Color.CYAN } ); model.addRow( new Object[]{ "Blue", Color.BLUE } ); model.addRow( new Object[]{ "Magenta", Color.MAGENTA } ); // Das JTable initialisieren JTable table = new JTable( model ); // Dem JTable sagen, wie Farben gerendert werden sollen table.setDefaultRenderer( Color.class, new ColorRenderer() ); // Die Tabelle anzeigen JFrame frame = new JFrame( "Demo" ); frame.getContentPane().add( new JScrollPane( table ) ); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.pack(); frame.setVisible( true ); }
} </code=java>
Darstellung auf Zeilen- oder Tabellen-Ebene: prepareRenderer
Normalerweise werden Renderer für eine bestimmte Datenklasse oder Datenspalte geschrieben.
Aber wie kann man Zeilen-abhängige oder Tabellen-abhängige Erfordernisse 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.
Das folgende Beispiel zeigt eine Tabelle, die die Zeilen abwechselnd färbt und den Rand der selektierten Zelle ändert:
<code=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); }
} </code=java>