jtable problem (das tausendste??)

Zero2000

Mitglied
Hallo,

nachdem ich mein JAVA LBD 2010 aus Zeitgründen abbrechen musste (PHP war zu diesem Zeitpunkt leider wichtiger), habe ich nun dieses Jahr wieder angefangen. Bin also nach wie vor Newbie.

Ich habe zum testen eine MySQL-Tabelle mit verschiedenen Datentypen angelegt und versuche diese auszulesen. Das klappt auch und in der Konsole kann ich mir die Daten auch wunderbar formatiert ausgeben lassen. Nun scheitert das ganze, dass ich diese Daten in einer Tabelle genauso angezeigt haben möchte. Ich lese mir seit Wochen die Augen wund über jtable, cellrenderer und tablemodel.

Ich habe natürlich einiges gefunden und ausprobiert, aber ehrlich, ich checks nich.
Nachfolgend mal ein Code den ich, wie soll es anders sein, im Netz gefunden habe.
Java:
package mysql.jtable;

import java.sql.*;
import java.util.Vector;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;

public class Main extends JFrame {
    private static final String DRIVER = "com.mysql.jdbc.Driver";
    private static final String CONNECTION = "jdbc:mysql://localhost:3306/db67769";
    private static final String USER = "root";
    private static final String PASS = "";
    private static final String QUERY = "SELECT * FROM i_kbuch";
            
    final ResultSetToTableModel model = new ResultSetToTableModel();
    
    Main() throws Exception {        
        // Add table to model
        add(new JScrollPane(new JTable(model)));
        // Connect to database, query result, set to database 
        try
        {
        Class.forName(DRIVER);
        Connection c = DriverManager.getConnection(CONNECTION,USER,PASS);
        ResultSet rs = c.createStatement().executeQuery(QUERY);        
        model.setResultSet(rs);
        }catch (Exception e){
            e.printStackTrace();
        }
        // Minimalistic frame initialization 
        pack();
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }
    
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                try {
                    new Main().setVisible(true);
                } catch (Exception ex) {
                    System.err.println(ex.getMessage());
                    ex.printStackTrace();
                }
            }
        });
    }
}
/** Simple resultset table model */
class ResultSetToTableModel extends AbstractTableModel {
    // Currently displayed result set
    private ResultSet rs;
    // Associated metadata
    private ResultSetMetaData rsMeta;
    // Column count
    private int columnCount;
    // Column names
    private final Vector<String> columnNames = new Vector<String>();
    // Vector of rows
    private final Vector<Object[]> cache = new Vector<Object[]>();
    /** Set new result set */
    public void setResultSet(ResultSet rs) throws SQLException {
        if (this.rs != null)
            this.rs.close();
        cache.clear();
        // 'Cache' some metadata data
        rsMeta = rs.getMetaData();
        columnCount = rsMeta.getColumnCount();
        columnNames.clear();
        for(int col = columnCount; col > 0; col--) {
            columnNames.add(rsMeta.getColumnName(col));
        }
        // 'Cache' new resultest data
        while(rs.next()) {
            Object rowData[] = new Object[columnCount];
            for(int col = columnCount; col > 0; col--)                 
                rowData[col - 1] = rs.getObject(col); 
            cache.add(rowData);
        }
        // Reload table structure
        fireTableStructureChanged();
    }
    /** Close result set */
    public void close() throws SQLException {
        rs.close();
    }
    /** ## TableModel interface ## */
    public String getColumnName(int column) {
        return columnNames.get(column);
    }
    /** ## TableModel interface ## */
    public Object getValueAt(int rowIndex, int columnIndex) {
        Object[] row = cache.get(rowIndex);
        return row[columnIndex];
    }
    /** ## TableModel interface ## */
    public int getRowCount() {
        return cache.size();
    }
    /** ## TableModel interface ## */
    public int getColumnCount() {
        return columnCount;
    }   
}
Ich schau sprichwörtlich wie die Sau ins Uhrwerk. Wenn ich das ausgeben lasse werden die Spaltennamen von hinten beginnend angezeigt und wie zur Hölle komme ich denn nun an meine Daten?

Ich weis das es bestimmt nervt aber gibt es jemanden der Geduld mit mir hat und das mal durchkaut?

Die Tabelle i_kbuch enthält Spalten mit Integeren Werten, Beträgen und Datum.

Maik
 
Ich habe (natürlich) Geduld mit dir😉

JTables holen ihre Daten, wie alle anderen Swing-Komponenten auch, aus einem Model. Ich hoffe, das du das Tutorial bereits angeschaut hast.

Bei JTable ist das ein TableModel. Es gibt eine abstrakte Klasse AbstractTableModel, die dir hilft, weil du damit "nur" noch die Methoden getRowCount(), getColumnCount(),getValueAt() überschreiben musst.

Ich überschreibe auch immer getColumnName() damit ich Spaltennamen habe, die "schöner" aussehen.

Da du deine Daten aus einer Datenbank liest, musst du also eine Klasse schreiben, die von AbstractTableModel erbt und mindestens die genannten Methoden implementiert. Dein Codebeispiel macht das ähnlich, benutzt aber noch alte Klassen wie Vector, wovon ich abrate.

Zum Test solltest du deine Klasse mit Dummy-Werten füllen und die JTable anzeigen lassen. Dann weisst du das die "Verdrahtung" passt.

Danach musst du "nur" aus dem Resultset von der DB die Daten übernehmen und in dein Model schieben.


Wie das in einem Beispiel aussehen könnte, zeige ich mal hier

Java:
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Random;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;

@SuppressWarnings("serial")
public class Table extends JPanel {
    private MeinTableModel tableModel;
    private List<Daten> listDaten;
    private JTable table;
    private JButton btnAdd;
    private Random random = new Random();

    public Table() {
	setLayout(new BorderLayout());
	// @formatter:off
		Daten[] memoryDaten = new Daten[] { 
			new Daten("Turtle", 20, false,"1.1.1993"),
			new Daten("Andreas", 10, false,"10.5.2003"),
			new Daten("B�rbel", 30, true,"20.02.1986"),
			new Daten("Christian", 40, false,"11.11.1980"),
			new Daten("Doris", 50, false,"22.02.1970"),
			new Daten("Emil", 66, true,"7.2.1966"),
			new Daten("Franziska", 70, true,"12.9.1955"),
			new Daten("Gerhard", 30, false,"16.08.1983"),
			new Daten("Heike", 33, false,"17.08.1986"),
			new Daten("Ingo", 22, false,"30.12.1992"),
		};
		// @formatter:on
	listDaten = Arrays.asList(memoryDaten);
	tableModel = new MeinTableModel(listDaten);
	table = new JTable(tableModel);
	table.setAutoCreateRowSorter(true);
	btnAdd = new JButton("Add/Reload");
	btnAdd.addActionListener(new ActionListener() {

	    @Override
	    public void actionPerformed(ActionEvent e) {
		int suffix = random.nextInt(10);
		// @formatter:off
		Daten[] neueDaten = new Daten[] { 
			new Daten("Turtle-"+suffix, 20, false, "1.1.1993"), 
			new Daten("Andreas-"+suffix, 10, false, "10.5.2003"),
			new Daten("B�rbel-"+suffix, 30, true, "20.02.1988"), 
			new Daten("Christian-"+suffix, 40, false, "11.11.1980"),
			new Daten("Doris-"+suffix, 50, false, "22.02.1970"), 
			new Daten("Emil-"+suffix, 66, true, "7.2.1966"),
			new Daten("Franziska-"+suffix, 70, true, "12.9.1955"), 
			new Daten("Gerhard-"+suffix, 30, false, "16.08.1983"),
			new Daten("Heike-"+suffix, 33, false, "17.08.1986"), 
			new Daten("Ingo-"+suffix, 22, false, "30.12.1992"), };
		// @formatter:on
		List<Daten> neuDaten = Arrays.asList(neueDaten);
		tableModel.addiereNeueDaten(neuDaten);

	    }
	});
	JScrollPane scrollPane = new JScrollPane(table);
	add(scrollPane, BorderLayout.CENTER);
	add(btnAdd, BorderLayout.SOUTH);
    }

    public static void main(String[] args) {
	JFrame frame = new JFrame("Table Test");
	frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	frame.add(new Table());
	frame.pack();
	frame.setVisible(true);
    }
}

@SuppressWarnings("serial")
class MeinTableModel extends AbstractTableModel {
    private List<Daten> listDaten;
    private List<String> listColumns;

    public MeinTableModel(List<Daten> daten) {
	this.listDaten = new ArrayList<Daten>();
	for (Daten datenSatz : daten) {
	    listDaten.add(datenSatz);
	}
	listColumns = new ArrayList<String>();
	listColumns.add("Name");
	listColumns.add("Alter");
	listColumns.add("Rentner");
	listColumns.add("Geburtstag");

    }

    public void addiereNeueDaten(List<Daten> neueDaten) {
	for (Daten daten : neueDaten) {
	    listDaten.add(daten);
	}
	fireTableStructureChanged();
    }

    @Override
    public int getRowCount() {
	return listDaten.size();
    }

    public Class<?> getColumnClass(int columnIndex) {
	switch (columnIndex) {
	case 0:
	    return String.class;
	case 1:
	    return Integer.class;
	case 2:
	    return Boolean.class;
	case 3:
	    return Date.class;
	}
	return null;
    }

    @Override
    public int getColumnCount() {
	return listColumns.size();
    }

    public String getColumnName(int column) {
	return listColumns.get(column);
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
	Daten daten = listDaten.get(rowIndex);
	if (daten != null) {
	    switch (columnIndex) {
	    case 0:
		return daten.getName();
	    case 1:
		return daten.getAlter();
	    case 2:
		return daten.isRentner();
	    case 3:
		return daten.getGeburtstag();
	    }
	}
	return null;
    }
}

class Daten {
    private String name;
    private Boolean rentner;
    private Date geburtstag;
    private Integer alter;
    private SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy");

    public Daten(String name, Integer alter, Boolean rentner, String geb) {
	try {
	    this.name = name;
	    this.alter = alter;
	    this.rentner = rentner;
	    this.geburtstag = sdf.parse(geb);
	} catch (ParseException e) {
	    e.printStackTrace();
	}
    }

    public String getName() {
	return name;
    }

    public void setName(String name) {
	this.name = name;
    }

    public Boolean isRentner() {
	return rentner;
    }

    public void setRentner(Boolean rentner) {
	this.rentner = rentner;
    }

    public Date getGeburtstag() {
	return geburtstag;
    }

    public void setGeburtstag(Date geburtstag) {
	this.geburtstag = geburtstag;
    }

    public int getAlter() {
	return alter;
    }

    public void setAlter(Integer alter) {
	this.alter = alter;
    }

}
 
vielen Dank für dein Beispiel und die Erklärung.
Im Groben und Ganzen habe ich das ja auch verstanden, aber ich glaube ich muss mir das nochmal anhand eines Tutorials step by step erklären lassen.

Ich verwende die net.proteanit.sql.DbUtils und kann, wenn ich mein Resultset an die jTable übergebe ja zum Beispiel die Breite der Spalten mit
Java:
tblktliste.getColumnModel().getColumn(3).setPreferredWidth(100);
ändern.

Gibt es denn eine Möglichkeit hier genauso wie die Breite auch die Werte zu formatieren?
Oder benötige ich dann doch ein eigenes Tablemodel?

LG
Maik
 
Prima, hab endlich meinen ersten eigenen TableCellRenderer geschrieben. Als Basis diente mir ein Tutorial in dem es um das einfärben von Zellen ging.

Nun habe ich dazu noch eine Frage.

Im Moment gebe ich die Spalte explizit an mit: if(col == 4)
gibt es auch eine Möglichkeit zu sagen z.B. if(value.Number) oder so?
 
Diese Nachfrage verstehe ich nicht.

Du hast doch die Methode
Java:
getTableCellRendererComponent(
                            JTable table, Object color,
                            boolean isSelected, boolean hasFocus,
                            int row, int column)
Also kannst du im TableModel nachschauen, welcher Wert da steht (getValueAt(row, column)) und davon abhängig deinen Renderer konfigurieren.
 
Hallo,

aber muß ich mit getValueAt() nicht auch explizit die Spaltennummer angeben?

So sieht das ganze bisher aus:
Java:
public class ColoredTable extends JFrame{
    
    //Hier beginnen die Daten für die Tabelle!
    String[] kopf = {
        "Spalte A", "Spalte B", "Spalte C", "Spalte D", "Spalte E", "Spalte F"
    };
    String[][] data = {            
        {"Zelle A1", "Zelle A2", "Zelle A3", "Zelle A4", "6523", "Zelle A6"},            
        {"Zelle B1", "Zelle B2", "Zelle B3", "Zelle B4", "200", "Zelle B6"},            
        {"Zelle C1", "Zelle B2", "hurz", "Zelle C4", "63258", "Zelle C6"},            
        {"Zelle D1", "Zelle D2", "Zelle D3", "Zelle D4", "119956", "Zelle D6"}
    };
    
    //Hier wird die Tabelle erzeugt und angezeigt!
    public ColoredTable() {
        
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setBounds(0, 0, 500, 300);
        setLocationRelativeTo(null);
        
        DefaultTableModel model = new DefaultTableModel();        
        model.setDataVector(data, kopf);        
        JTable table = new JTable(model);
        this.add(new JScrollPane(table));
        
        table.setDefaultRenderer(Object.class, new ColorRenderer());
        table.setRowHeight(35);
    }
    
    //Hier wird das Programm ausgeführt!
    public static void main(String[] args) {        
        new ColoredTable().setVisible(true);    
    }        

    //Nachfolgend der ColorRenderer der für die Farben zuständig ist!
    class ColorRenderer extends JLabel implements TableCellRenderer {        
        public ColorRenderer() {            
            this.setOpaque(true);            
            this.setHorizontalAlignment(JLabel.CENTER);        
        }
    
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
                
            this.setText(value.toString());
            
            Color graublau = new Color(50, 170, 200);
            
            // Verändern aufgrund der Position           
            if ((row%2==0 && col%2==0)|| (row%2==1 && col%2==1))              
                this.setBackground(Color.GREEN.darker());            
            else                
                this.setBackground(graublau);
            
            //Verändern aufgrund des Zelleninhalts          
            if (value.toString().endsWith("2"))           
                this.setForeground(Color.BLUE);          
            else              
                this.setForeground(Color.WHITE);
            
            if (value.toString().equals("hurz")) {
                this.setBackground(Color.ORANGE); 
                this.setForeground(Color.BLACK);
            }
            // Verändern aufgrund der Selektion      
            if (isSelected) {              
                this.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1));
                this.setBackground(Color.LIGHT_GRAY);
            }else           
                this.setBorder(null);
            
            if(col == 4)
            {
                NumberFormat df = NumberFormat.getCurrencyInstance();
                this.setText(df.format(Double.parseDouble(value.toString())));
                this.setHorizontalAlignment(JLabel.RIGHT);
            }else{
                this.setHorizontalAlignment(JLabel.CENTER);
            }
            
            return this;       
        }    
    }
}
 
Ich möchte erreichen, dass entweder eine bestimmte Spalte oder float-Werte als Fließkommazahl bzw. Währung formatiert in einem JTable angezeigt wird. Die Daten kommen aus einer MySQL-DB.
 
Das ist doch schon alles enthalten, was du hast.

So setzt du einen Renderer für Spalte 4, in der bei mir ein double-Wert steht.
Java:
	TableColumnModel tcm = table.getColumnModel();
	TableColumn tc = tcm.getColumn(4);
	tc.setCellRenderer(new CurrencyRenderer());
So sagst du, das in Spalte 4 numerische Werte stehen
Java:
    public Class<?> getColumnClass(int columnIndex) {
	switch (columnIndex) {
	case 0:
	    return String.class;
	case 1:
	    return Integer.class;
	case 2:
	    return Boolean.class;
	case 3:
	    return Date.class;
	case 4:
	    return Number.class;
	}
	return null;
    }
So setzt du die Werte aus dem Model in die JTable
Java:
    public Object getValueAt(int rowIndex, int columnIndex) {
	Daten daten = listDaten.get(rowIndex);
	if (daten != null) {
	    switch (columnIndex) {
	    case 0:
		return daten.getName();
	    case 1:
		return daten.getAlter();
	    case 2:
		return daten.isRentner();
	    case 3:
		return daten.getGeburtstag();
	    case 4:
		return daten.getGehalt();
	    }
	}

Bleibt noch den Renderer zu schreiben
Java:
class CurrencyRenderer extends JLabel implements TableCellRenderer {

    public CurrencyRenderer() {
	super();
    }

    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {

	if (isSelected) {
	    setForeground(table.getSelectionForeground());
	    super.setBackground(table.getSelectionBackground());
	} else {
	    setForeground(table.getForeground());
	    setBackground(table.getBackground());
	}
	Locale currentLocale = Locale.GERMAN;
	NumberFormat numberFormatter = NumberFormat.getNumberInstance(currentLocale);
	Double quantity = (Double) value;
	setText(numberFormatter.format(quantity));
	setHorizontalAlignment(RIGHT);
	return this;
    }
}
 
vielen Dank, das hat mir sehr geholfen und mein Renderer steht soweit.
Ich hätte da noch eine Frage:

Gibt es eine Möglichkeit abzufragen welcher Datentyp sich in welcher Reihe oder Zelle (get ValueAt??) befindet um dann dem Renderer mitteilen zu können (setValueAt???) wie er welche Zellen zu formatieren hat? Oder muss ich für jede Tabelle (4 Stk.) einen Renderer schreiben?

Im Moment formatiert er mir alle Zellen die (value instanceof Number) sind, was natürlich blöd ist da ich in manchen Zellen auch numerische Werte stehen hab die so bleiben sollen wie sie sind.

Mein Renderer schaut so aus:
Java:
import java.awt.Color;
import java.awt.Component;
import java.text.NumberFormat;
import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.table.TableCellRenderer;

class CurrencyRenderer extends JLabel implements TableCellRenderer {     
    
    public CurrencyRenderer() {    
        super();
    }    
    
    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
        
        Color graublau = new Color(55,105,140);
        Color lichtgrau = new Color(235,235,235);
        
        setOpaque(true);
        setBackground(row%2==0 ? Color.WHITE : lichtgrau);
          setForeground(Color.BLACK);
          setText((value != null)?value.toString():"");
          if (isSelected) {
             //setBorder(BorderFactory.createLineBorder(Color.BLACK));
             setBackground(graublau);
             setForeground(Color.WHITE);
          }else {
             setBorder(BorderFactory.createLineBorder(table.getBackground()));
          }
          
        if(value instanceof Number){
            NumberFormat df = NumberFormat.getCurrencyInstance();
            setText(df.format(Double.parseDouble(value.toString())));   
            setHorizontalAlignment(RIGHT);
        }else{
            setText((value != null)?value.toString():"");
            setHorizontalAlignment(LEFT);
        }
        
        return this;    
    }
}
 
Java:
getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)

column bzw.row auswerten?
 
Das habe ich bis heute versucht, aber ich scheitere an den Basics, mal wieder.:rtfm:
Ich veruche mit getColumnType den Typ zu erfragen, aber ich habe ein generelles Verständnissproblem.
Um an den ColumnType zu kommen, benötige ich doch die Metadaten aus dem Resultset??
Aber wie bekomme ich die Metadaten aus meiner Hauptklasse in den Renderer?? :autsch:
 
Das verstehe ich nicht.

Du instanzierst doch "irgendwo" deinen Renderer.
Java:
TableColumnModel tcm = table.getColumnModel();
	TableColumn tc = tcm.getColumn(4);
	tc.setCellRenderer(new CurrencyRenderer());
Also kannst du in den Konstruktor von CurrencyRenderer alles angeben, was du benötigst, also auch dein ResultSet.

Java:
tc.setCellRenderer(new CurrencyRenderer(dasResultSet));
 
Zuletzt bearbeitet:

Zurück
Oben