JTable - Teil 5 - Verändern von Daten

Status
Nicht offen für weitere Antworten.
B

Beni

Gast
JTable - Teil 5 - Verändern von Daten

Am Ende des 2. Teils wurde gezeigt, dass man auch Daten eingeben kann. Jetzt soll das nocheinmal vertieft behandelt werden.

1. Der einfache Weg

Das JTable unterstützt von sich aus das Editieren von Zahlen und von Booleans. Alles andere wird wie ein String behandelt, d.h. da der Benutzer nur einen Text eingeben kann, wird dem Model ein String übergeben, welches es selbst in ein Objekt übersetzen muss.

Die Verwendung der Table-eigenen Editoren ist einfach:

Zuerst die Tabelle
Java:
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 );
   }
}

Jetzt das Model. Jede Zelle ist editierbar, doch jede Column wird anders behandelt.
Java:
class Model extends AbstractTableModel{
	private String[] strings;
	private int[] integers;
	private double[] doubles;
	private boolean[] booleans;
	
   	public Model(){
   		strings = new String[]{ "hallo", "ich", "bin", "eine", "Demo" };
   		integers = new int[]{ 1, 2, 3, 4, 5 };
   		doubles = new double[]{ 1.2, 3.4, 5.6, 7.8, 9.11 };
   		booleans = new boolean[]{ true, false, false, true, true };
   	}
   
   	// Die Anzahl Columns
   	public int getColumnCount() {
   		return 4;
   	}
   
   	// Die Anzahl Rows
   	public int getRowCount() {
   		return 5;
   	}
   
   	// Die Titel der einzelnen Columns
   	public String getColumnName(int column) {
   		switch( column ){
   			case 0: return "String";
   			case 1: return "Integer";
   			case 2: return "Double";
   			case 3: return "Boolean";
   			default: return null;
   		}
   	}
   
   	// Der Wert der Zelle (rowIndex, columnIndex)
   	public Object getValueAt(int rowIndex, int columnIndex) {
   		switch( columnIndex ){
   			case 0: return strings[ rowIndex ];
   			case 1: return new Integer( integers[ rowIndex ] );
   			case 2: return new Double( doubles[ rowIndex ] );
   			case 3: return new Boolean( booleans[ rowIndex ] );
   			default: return null;
   		}
   	}

   	// Eine Angabe, welchen Typ von Objekten in den Columns angezeigt werden soll
   	public Class getColumnClass(int columnIndex) {
   		switch( columnIndex ){
   			case 0: return String.class;
   			case 1: return Integer.class;
   			case 2: return Double.class;
   			case 3: return Boolean.class;
   			default: return null;
   		}
   	}
   	
   	// Jede Zelle ist editierbar
	public boolean isCellEditable(int rowIndex, int columnIndex) {
		return true;
	}
	
	// Wird aufgerufen, falls der Wert einer Zelle verändert wurde
	public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
		switch( columnIndex ){
			case 0:
				strings[ rowIndex ] = aValue.toString();
				break;
			case 1:
				integers[ rowIndex ] = ((Integer)aValue).intValue();
				break;
			case 2:
				doubles[ rowIndex ] = ((Double)aValue).doubleValue();
				break;
			case 3:
				booleans[ rowIndex ] = ((Boolean)aValue).booleanValue();
		}
	}
}

beni-albums-jtable-picture62-table-10.png


Es wichtig, dass man nicht vergisst, dass dies ausschliesslich für Strings (oder Objekte die als String dargestellt werden können, z.B. ein Datum), Nummern und Booleans so funktioniert!

2. Mit Hilfe eines TableCellEditors
Es ist möglich, eigene Editoren einzusetzen. Die Editoren werden vom JTable mit allen möglichen Informationen versorgt, welche sie irgendwie darstellen müssen. Die JTable wird dann warten, bis der Editor meldet, dass alle Daten eingegeben wurden.

Zuerst wird ein JTable aufgesetzt. Mit "setDefaultEditor" wird ein Editor mit einem Typ verbunden. Z.B. könnte der IntegerEditor mit "Integer.class" verbunden werden. Der Typ bezieht sich auf die Rückgabe von TableModel.getColumnClass.
Java:
import java.awt.Color;
import java.awt.Component;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.EventObject;
import java.util.List;

import javax.swing.*;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;

public class JTableDemo{
    public static void main( String[] args ){
        // Einige Testdaten generieren
        DefaultTableModel model = new DefaultTableModel( new Object[]{ "A", "B" }, 0);
        model.addRow( new Object[]{ "1", "2" });
        model.addRow( new Object[]{ "3", "4" });
        model.addRow( new Object[]{ "5", "6" });
        model.addRow( new Object[]{ "7", "8" });
        JTable table = new JTable( model );
        // Den TestEditor einbinden. Er wird alle Spalten die als Typ
        // "Object" haben, bearbeiten.
        table.setDefaultEditor( Object.class, new TestEditor());
        
        // Die Tabelle anzeigen
        JFrame frame = new JFrame();
        frame.add( new JScrollPane( table ));
        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        frame.pack();
        frame.setLocationRelativeTo( null );
        frame.setVisible( true );
    }
}

Der Editor selbst ist eine ziemlich unabhängige Komponente. Oft ist es am einfachsten, direkt von einer Component zu erben, und zusätzlich das Interface TableCellEditor zu implementieren.
Java:
//Der Editor selbst ist ein einfaches JTextField
class TestEditor extends JTextField implements TableCellEditor, DocumentListener{
    private List<CellEditorListener> listeners = new ArrayList<CellEditorListener>();
    
    public TestEditor(){
        // Der Editor hört sich selbst ab, so kann er auf jede Benutzereingabe reagieren
        getDocument().addDocumentListener( this );
    }

    public Component getTableCellEditorComponent( JTable table, Object value, boolean isSelected, int row, int column ) {
        // Diese Methode wird von der JTable aufgerufen, wenn der Editor angezeigt werden soll
        setText( value.toString() );
        return this;
    }

    public void addCellEditorListener( CellEditorListener l ) {
        listeners.add( l );
    }

    public void cancelCellEditing() {
        // Falls abgebrochen wird, werden alle Listeners informiert
        ChangeEvent event = new ChangeEvent( this );
        for( CellEditorListener listener : listeners.toArray( new CellEditorListener[ listeners.size() ] ))
            listener.editingCanceled( event );
    }

    public Object getCellEditorValue() {
        // Gibt den aktuellen Wert des Editors zurück
        return getText();
    }

    public boolean isCellEditable( EventObject anEvent ) {
        // Im Falle eines MouseEvents, muss ein Doppelklick erfolgen, um den Editor zu aktivieren.
        // Ansonsten wird der Editor auf jeden Fall aktiviert
        if( anEvent instanceof MouseEvent )
            return ((MouseEvent)anEvent).getClickCount() > 1;
            
        return true;
    }

    public void removeCellEditorListener( CellEditorListener l ) {
        listeners.remove( l );
    }

    public boolean shouldSelectCell( EventObject anEvent ) {
        return true;
    }

    public boolean stopCellEditing() {
        // Sollte die Eingabe falsch sein, darf das editieren nich gestoppt werden
        if( !isValidText() )
            return false;
        
        // Ansonsten werden die Listener vom stop unterrichtet
        ChangeEvent event = new ChangeEvent( this );
        for( CellEditorListener listener : listeners.toArray( new CellEditorListener[ listeners.size() ] ))
            listener.editingStopped( event );
        
        return true;
    }

    public void changedUpdate( DocumentEvent e ) {
        update();
    }

    public void insertUpdate( DocumentEvent e ) {
        update();
    }

    public void removeUpdate( DocumentEvent e ) {
        update();
    }
    
    private boolean isValidText(){
        // Bestimmt, was eine gültige Eingabe ist.
        return getText().matches( "[1-9]+" );
    }
    
    public void update(){
        // Verändert die Umrandung des Editors, jenachdem, ob eine gültige
        // oder eine ungültige Eingabe gemacht wurde
        Color color;
        if( isValidText() )
            color = Color.GREEN;
        else
            color = Color.RED;
        
        setBorder( BorderFactory.createLineBorder( color ));
    }
}

3. TableCellEditor für eigene Typen
Schon in Teil 3 wurde gezeigt, dass das JTable Objekte des Types "Date" nicht richtig unterstützt. Das soll jetzt geändert werden.

Die Tabelle, man beachte, dass ein "DateRenderer" und ein "DateEditor" registriert werden.
Java:
import java.awt.Component;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.EventObject;
import java.util.Vector;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JTable;
import javax.swing.SpinnerDateModel;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellEditor;

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 );
      
      // Renderer und Editor für "Date" werden registriert
      table.setDefaultRenderer( Date.class, new DateRenderer() );
      table.setDefaultEditor( Date.class, new DateEditor() );
            
      JFrame frame = new JFrame( "Demo" );
      frame.getContentPane().add( new JScrollPane( table ) );
      
      frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
      frame.pack();
      frame.setVisible( true );
   }
}

Die Tabelle soll was sinnvolles darstellen: Notizen die aus einem Datum und aus einem Ereignis bestehen
Java:
// Eine einfache Klasse die Notizen beschreibt
class Note{
	private Date date;
	private String event;
	
	public Note( String event, Date date ){
		setEvent( event );
		setDate( date );
	}
	
	public String getEvent(){ return event; }
	public Date getDate(){ return date; }
	
	public void setEvent( String event ){ this.event = event; }
	public void setDate( Date date ){ this.date = date; }
}

Das TableModel, man beachte, dass es keine Verbindungen zum Renderer oder zum Editor besitzt! Allerdings kann durch das Wissen, dass "Date" nun unterstützt wird, in der Methode "setValueAt" ein Cast "Object -> Date" durchgeführt werden.
Java:
class Model extends AbstractTableModel{
	private Note[] notes;
	
   	public Model(){
   		notes = new Note[]{
   				new Note( "Mit Hund rausgehen", new Date(  104, 5, 2, 14, 0 ) ),
				new Note( "Kino", new Date( 104, 5, 7, 21, 30 ) ),
				new Note( "Abschlussprüfung", new Date( 104, 8, 14, 7, 0 ) ),
				new Note( "Einkaufen gehen", new Date( 104, 5, 2, 16, 0 ) ),
				new Note( "Rechnung bezahlen", new Date( 110, 11, 30, 23, 59 ) ),
   		};
   	}
   
   	// Die Anzahl Columns
   	public int getColumnCount() {
   		return 2;
   	}
   
   	// Die Anzahl Rows
   	public int getRowCount() {
   		return 5;
   	}
   
   	// Die Titel der einzelnen Columns
   	public String getColumnName(int column) {
   		if( column == 0 )
   			return "Ereignis";
   		else
   			return "Datum";
   	}
   
   	// Der Wert der Zelle (rowIndex, columnIndex)
   	public Object getValueAt(int rowIndex, int columnIndex) {
   		if( columnIndex == 0 )
   			return notes[ rowIndex ].getEvent();
   		else
   			return notes[ rowIndex ].getDate();
   	}

   	// Eine Angabe, welchen Typ von Objekten in den Columns angezeigt werden soll
   	public Class getColumnClass(int columnIndex) {
   		if( columnIndex == 0 )
   			return String.class;
   		else
   			return Date.class;
   	}
   	
   	// Jede Zelle ist editierbar
	public boolean isCellEditable(int rowIndex, int columnIndex) {
		return true;
	}
	
	// Wird aufgerufen, falls der Wert einer Zelle verändert wurde
	// Der Wert kommt entweder als String, oder von dem 
	// DateEditor (und daher als Date). 
	public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
		if( columnIndex == 0 )
			notes[ rowIndex ].setEvent( (String)aValue );
		else
			notes[ rowIndex ].setDate( (Date)aValue );
	}
}

Der Renderer, der "Date" auf eine brauchbare Art darstellt ist einfach gehalten:
Java:
class DateRenderer extends DefaultTableCellRenderer{
	private DateFormat format; 
	
	public DateRenderer(){
		format = DateFormat.getDateTimeInstance();
	}
	
	public Component getTableCellRendererComponent(JTable table, Object value,
			boolean isSelected, boolean hasFocus, int row, int column) {

		// Date's werden auf einem einfache Weg in lesbare Texte umgewandelt.
		String text = format.format( (Date)value );
		
		return super.getTableCellRendererComponent(table, text, isSelected,
				hasFocus, row, column);
	}
}

Der Editor. Der Editor erbt von JSpinner. Glücklicherweise unterstützt der JSpinner "Date", und so kann man das meiste an den JSpinner delegieren
Java:
class DateEditor extends JSpinner implements TableCellEditor{
	private SpinnerDateModel model;
	
	private List listeners = new ArrayList();
	
	// Standardkonstruktor
	public DateEditor(){
		model = new SpinnerDateModel();
		model.setCalendarField( Calendar.DAY_OF_YEAR );
		setModel( model );
	}
	
	// Möglicherweise möchte jemand über Ereignisse des Editors
	// informiert werden
	public void addCellEditorListener(CellEditorListener l) {
		listeners.add( l );
	}
	
	// Ein CellEditorListener entfernen
	public void removeCellEditorListener(CellEditorListener l) {
		listeners.remove( l );
	}
	
	// Gibt den aktuellen Wert des Editors zurück.
	public Object getCellEditorValue() {
		return model.getDate();
	}
	
	// Gibt eine Component zurück, welche auf dem JTable dargestellt wird,
	// und mit der der Benutzer interagieren kann.
	public Component getTableCellEditorComponent(JTable table, Object value,
			boolean isSelected, int row, int column) {
		
		model.setValue( value );
		return this;
	}
	
	// Gibt an, ob die Zelle editierbar ist. Das EventObject kann
	// ein MouseEvent, ein KeyEvent oder sonst was sein.
	public boolean isCellEditable(EventObject anEvent) {
		return true;
	}
	
	// Gibt an, ob die Editor-Component selektiert werden muss, um
	// sie zu benutzen. Diese Editor soll immer selektiert werden,
	// deshalb wird hier true zurückgegeben
	public boolean shouldSelectCell(EventObject anEvent) {
		return true;
	}
	
	// Bricht das editieren der Zelle ab
	public void cancelCellEditing() {
		fireEditingCanceled();
	}
	
	// Stoppt das editieren der Zelle, sofern möglich.
	// Da der JSpinner immer einen gültigen Wert anzeigt, kann auch 
	// jederzeit gestoppt werden (return-Wert = true)
	public boolean stopCellEditing() {
		fireEditingStopped();
		return true;
	}
	
	// Benachrichtig alle Listener, dass das Editieren abgebrochen wurde
	protected void fireEditingCanceled(){
		ChangeEvent e = new ChangeEvent( this );
		for( int i = 0, n = listeners.size(); i<n; i++ )
			((CellEditorListener)listeners.get( i )).editingCanceled( e );
	}
	
	// Benachrichtig alle Listener, dass das Editieren beendet wurde
	protected void fireEditingStopped(){
		ChangeEvent e = new ChangeEvent( this );
		for( int i = 0, n = listeners.size(); i<n; i++ )
			((CellEditorListener)listeners.get( i )).editingStopped( e );
	}
}

beni-albums-jtable-picture63-table-11.png


Also: der Editor ist eine Component die auf das JTable gesetzt wird, einen bestimmten Wert zugewiesen bekommt, und diesen Wert verändern darf. Klickt der Benutzer an eine andere Stelle, wird das Editieren entweder abgebrochen (cancelCellEditing) oder beendet (stopCellEditing), und der Wert dem TableModel über die Methode setValueAt überwiesen.

© 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