JTable (Java API): Unterschied zwischen den Versionen
K (→Darstellung auf Zeilen- oder Tabellen-Ebene: prepareRenderer) |
K |
||
Zeile 25: | Zeile 25: | ||
Eine sehr einfache Art und Weise ist ein 2-dimensionaler [[Array]]. Dieser Array wird der ''JTable'' direkt im [[Konstruktor_(Java)|Konstruktor]] übergeben. Ein Array von [[String|Strings]] gibt die Titel der Spalten an. | Eine sehr einfache Art und Weise ist ein 2-dimensionaler [[Array]]. Dieser Array wird der ''JTable'' direkt im [[Konstruktor_(Java)|Konstruktor]] übergeben. Ein Array von [[String|Strings]] gibt die Titel der Spalten an. | ||
Die ''JTable'' wird die [[Methode_(Java)|Methode]] ''toString()'' benutzen, um die Objekte in dem Array in Text umzuwandeln. | Die ''JTable'' wird die [[Methode_(Java)|Methode]] ''toString()'' benutzen, um die Objekte in dem Array in Text umzuwandeln. | ||
− | < | + | <syntaxhighlight lang="java"> |
import javax.swing.JFrame; | import javax.swing.JFrame; | ||
import javax.swing.JScrollPane; | import javax.swing.JScrollPane; | ||
Zeile 53: | Zeile 53: | ||
frame.setVisible( true ); | frame.setVisible( true ); | ||
} | } | ||
− | } </ | + | } </syntaxhighlight> |
==== DefaultTableModel ==== | ==== DefaultTableModel ==== | ||
Sehr einfach kann man mit einem [[TableModel (Java API)|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'', ...). | Sehr einfach kann man mit einem [[TableModel (Java API)|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'', ...). | ||
− | < | + | <syntaxhighlight lang="java"> |
import java.awt.BorderLayout; | import java.awt.BorderLayout; | ||
import java.awt.event.ActionEvent; | import java.awt.event.ActionEvent; | ||
Zeile 117: | Zeile 117: | ||
} | } | ||
} | } | ||
− | </ | + | </syntaxhighlight> |
==== Eigenes TableModel ==== | ==== Eigenes TableModel ==== | ||
Zeile 127: | Zeile 127: | ||
Zuerst die Definition einer Flasche: | Zuerst die Definition einer Flasche: | ||
− | < | + | <syntaxhighlight lang="java">// Eine Klasse deren Inhalt jeweils in einer Zeile |
// dargestellt wird | // dargestellt wird | ||
public class Bottle{ | public class Bottle{ | ||
Zeile 158: | Zeile 158: | ||
this.size = size; | this.size = size; | ||
} | } | ||
− | }</ | + | }</syntaxhighlight> |
Nun das ''TableModel'' welches Flaschen verarbeiten kann: | Nun das ''TableModel'' welches Flaschen verarbeiten kann: | ||
− | < | + | <syntaxhighlight lang="java">// Ein Model das Bottle-Objekte aufnimmt, und pro Zeile jeweils |
// ein Bottle-Objekt ausliest. | // ein Bottle-Objekt ausliest. | ||
public class BottleModel implements TableModel{ | public class BottleModel implements TableModel{ | ||
Zeile 266: | Zeile 266: | ||
} | } | ||
} | } | ||
− | }</ | + | }</syntaxhighlight> |
Und nun eine Klasse die das alles sichtbar macht: | Und nun eine Klasse die das alles sichtbar macht: | ||
− | < | + | <syntaxhighlight lang="java">public class JTableDemo{ |
public static void main( String[] args ){ | public static void main( String[] args ){ | ||
// Ein neues Model erzeugen | // Ein neues Model erzeugen | ||
Zeile 290: | Zeile 290: | ||
frame.setVisible( true ); | frame.setVisible( true ); | ||
} | } | ||
− | }</ | + | }</syntaxhighlight> |
=== Darstellung - TableCellRenderer === | === Darstellung - TableCellRenderer === | ||
Zeile 302: | Zeile 302: | ||
Das folgende Beispiel zeigt eine Tabelle die einige Farben auflistet. Zuerst die benötigten Imports: | Das folgende Beispiel zeigt eine Tabelle die einige Farben auflistet. Zuerst die benötigten Imports: | ||
− | < | + | <syntaxhighlight lang="java">import java.awt.Color; |
import java.awt.Component; | import java.awt.Component; | ||
import java.awt.Graphics; | import java.awt.Graphics; | ||
Zeile 313: | Zeile 313: | ||
import javax.swing.table.DefaultTableModel; | import javax.swing.table.DefaultTableModel; | ||
import javax.swing.table.TableCellRenderer; | import javax.swing.table.TableCellRenderer; | ||
− | </ | + | </syntaxhighlight> |
Nun folgt der ''TableCellRenderer'' der ''Color''-Objekte verarbeitet. | Nun folgt der ''TableCellRenderer'' der ''Color''-Objekte verarbeitet. | ||
− | < | + | <syntaxhighlight lang="java"> |
// Ein Renderer der Color-Objekte darstellt | // Ein Renderer der Color-Objekte darstellt | ||
public class ColorRenderer extends JLabel implements TableCellRenderer{ | public class ColorRenderer extends JLabel implements TableCellRenderer{ | ||
Zeile 368: | Zeile 368: | ||
} | } | ||
} | } | ||
− | }</ | + | }</syntaxhighlight> |
Schlussendlich wird alles miteinander in Verbindung gebracht: | Schlussendlich wird alles miteinander in Verbindung gebracht: | ||
− | < | + | <syntaxhighlight lang="java"> |
public class JTableDemo{ | public class JTableDemo{ | ||
public static void main( String[] args ){ | public static void main( String[] args ){ | ||
Zeile 414: | Zeile 414: | ||
} | } | ||
} | } | ||
− | </ | + | </syntaxhighlight> |
==== Darstellung auf Zeilen- oder Tabellen-Ebene: prepareRenderer ==== | ==== Darstellung auf Zeilen- oder Tabellen-Ebene: prepareRenderer ==== | ||
Zeile 432: | Zeile 432: | ||
Das folgende Beispiel zeigt eine Tabelle, die die Zeilen abwechselnd färbt und den Rand der selektierten Zelle ändert: | Das folgende Beispiel zeigt eine Tabelle, die die Zeilen abwechselnd färbt und den Rand der selektierten Zelle ändert: | ||
− | < | + | <syntaxhighlight lang="java">import java.awt.*; |
import java.text.*; | import java.text.*; | ||
import java.util.*; | import java.util.*; | ||
Zeile 481: | Zeile 481: | ||
} | } | ||
} | } | ||
− | </ | + | </syntaxhighlight> |
==Weiterführende Artikel== | ==Weiterführende Artikel== |
Version vom 15. März 2018, 18:08 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.
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 );
}
}
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, ...).
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 );
}
}
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:
// 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;
}
}
Nun das TableModel welches Flaschen verarbeiten kann:
// 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" );
}
}
}
Und nun eine Klasse die das alles sichtbar macht:
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 );
}
}
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:
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;
Nun folgt der TableCellRenderer der Color-Objekte verarbeitet.
// 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 Opazität (Gegenteil von 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 );
}
}
}
Schlussendlich wird alles miteinander in Verbindung gebracht:
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.add( new JScrollPane( table ) );
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.pack();
frame.setVisible( true );
}
}
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:
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);
}
}