JTable (Java API)

Aus Byte-Welt Wiki
(Weitergeleitet von JTable)
Zur Navigation springenZur Suche springen

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.

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:

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, ...).

JTable-Demo-add1.png
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);
    }
}

Weiterführende Artikel

Links