Excel/OpenOffice-Tabelle in Swing nachbauen

Hallo JTable-Spezialisten,

ich hab Schwierigkeiten beim Programmieren einer JTable.
Ich hab in der Firma eine Excel-Tabelle, die ich gerne in eine Swing-Version überführen möchte. Die Daten für die JTable sollen aus einer Datenbank kommen. Nun habe ich Probleme beim Aufbau eines passenden TableModels und beim Erzeugen einer solchen Tabelle:

Hat jemand schon mal eine ähnliche Tabelle programmiert?

Kann mich jemand mit ein paar Tipps oder besser noch Beispielcode auf den richtigen Weg bringen?

Der Mitarbeitername ist Teil einer DB-Tabelle „Mitarbeiter“, die Funktion ist Teil der Tabelle „Funktion“ und für die Schichteinsätze hab ich erst mal keinen Plan.
Wahrscheinlich müsste ich eine DB-Tabelle aufbauen, in der Daten (Date) verarbeitet werden und Fremdschlüssel auf Mitarbeiter drin sind. Daraus könnte ich dann ermitteln, an welchem Datum welcher Mitarbeiter welchen Dienst schieben muss.
Kann dazu noch jemand was sagen?

Wie sollte ich hier das TableModel für die JTable aufbauen?

Danke!

Also ich denke zum ersten muss man hier zwischen den beiden Problemen unterscheiden - wie an die Daten kommen und wie die Daten anzeigen.

Wichtig ist hierbei, dass du rausfindest, wie bzw wo es vermerkt ist, wer welche Schicht hat.

oder ob das nach einem gewissen Muster geht… Team A fängt mit N an, Team B mit F und Team C mit S und dann sich die Schichten immer in der Folge N -> F -> S abwechseln.

Falls es schon in einer Tabelle vermerkt ist, so musst du keine neue tabelle erstellen.

Falls es nach dem Muster läuft ebenso nicht, dann würde ich das im Programm ermitteln.

Das Tabellenmodel dürfte gar nicht mal so komplex sein (was noch alles daran hängt dann schon eher).

Ich weiss leider nicht wie man colspan in jtables erzeugt (also eine Zelle geht über mehrere Spalten).

Also um eine solche Tabelle zu haben brauchst du auf alle Fälle (mal als art brainstorming… ohne garantie)

  1. eine JTable
  2. ein TableModel
  3. ein DefaultTableCellRenderer
  4. controller klasse zum füllen

zu 1) die jtable - klar die fügt die einzelnen komponenten 2) und 3) zusammen - bestimmt größe der zellen, eventuelle MouseListener etc

zu 2) das ist abhängig von der jtable… ich würde ein einfache Klasse von DefaultTableModel erben lassen, das definiert, wieviele spalten und wieviele zeilen es gibt und wie die die Spaltenheader heissen… weiss nicht ob da noch viel mehr rein muss

zu 3) der muss schaun um welche spalte/zelle es sich handelt. je nach dem soll es dann grün/gelb/rot gezeichnet werden.

zu 4) hier ist dann der komplexe und entscheidende Teil. Der Controller liest z.b. aus der DB die Informationen, ordnet sie und bringt sie in eine passende Struktur. Danach läuft er über diese Informationen und füllt die Tabelle bzw. das Model.

mhm - weiss nicht wirklich ob das nun viel hiflt - das ganze ist meiner ansicht nach zu komplex, als dass man es in einem kurzen Forenbeitrag hier lösen könnte…

daher mein erster Vorschlag:

Schau wie du an die Daten rankommst, dass du alle hast die angezeigt werden sollen.

wenn das soweit ist, dann müssen wir uns eine geeignete Struktur für die daten überlegen.

dann kommt erst die Tabelle

Viel erfolg
dbc

Also sowit ich weiß geht das verbinden von Zellen nicht, zumindestens habe ich nur mal ein WorkAround gelesen in dem einfach ein entsprechender CellRenderer eingesetzt wurde der einfach mehrere Zellen „übermalt“ hat.
Es gibt aber die Möglichkeit TabellenHeader zuverbinden: http://www.esus.com/javaindex/j2se/jdk1.2/javaxswing/editableatomiccontrols/jtable/jtablespanheaders.html

Angesichts dessen würde ich für jedes Team eine eigene JTable(ggf. eigene Version ableiten) machen und dann darin die Daten publizieren. Die Daten würde ich ebenfalls in eine Datenbank speichern, um so z.B. die Dienste mal ändern zukönnen.

Was die Tabelle in OOCalc angeht ist dies imho eine Eigenentwicklung der OO.org und somit keine Ableitung der vorhanden JTable. Außerdem ist der Kern und einige Komponenten von OO sowieso in C++ geschrieben, vielleicht auch die Table?
OpenOffice ist doch eigentlich Open Source, vielleicht sollte man da einfach mal reinschauen wie es da umgesetzt wurde.

Gut Schuß
VuuRWerK :wink:

Hier ist mal eine kleine Demo, wie es eventuell aussehen könnte (die Daten werden einfach nur im Programm generiert):
http://www.java-forum.org/de/userfiles/user3690/Einsatzplan.jar (Quellcode im jar)

André!!! Jaa!!! gröhl :smiley:
Du bist der Größte! Respekt!
Ich bin begeistert. Das bringt mich einen riesigen Schritt nach vorn!
Danke!

André’s Programmbeispiel hier noch mal zum Vergleich, falls jemand daran interessiert ist und die Quelle nicht mehr verfügbar sein sollte:

Dann werde ich mich ran machen, das zu verstehen, was du so prima umgesetzt hast.
Für dieses Projekt kommen sicher noch mehr Fragen, aber diese kann ich schon mal abhaken. :slight_smile:

Hallo André,

kannst du mir bitte noch ein Beispiel „montieren“, in dem das

im Header der JTabel sitzt?
Das Problem ist jetzt nämlich noch, dass der Kopf der Tabelle aus dem Sichtbereich gescrollt werden kann, wenn ich weitere Mitarbeiter einfüge und der Platz im Fenster nicht mehr ausreicht.

Folgendes Beispiel benutzt eine zweite MultiSpanCellTable für die Kopfzeilen, die fixiert bleiben sollen:
http://www.java-forum.org/de/userfiles/user3690/Einsatzplan2.jar (Quellcode im jar)

Sehr schön André! Danke.
Warum hast du das in eine eigene Tabelle gezeichnet? Bedeutet das, dass es Schwierigkeiten mit dem Header beim Zeichnen mehrzeiliger/-spaltiger Tabellenköpfe gibt?

Ich habe das nicht untersucht, könnte es aber u.U. machen.
Ich ging erstmal davon aus, dass eine zweite Tabelle im vorliegenden Fall die einfachste Lösung ist.

(In Excel wird der eigentliche Header übrigens auch nicht verändert; man fixiert lediglich die Kopfzeilen.
In meinem Beispiel setze ich aber TableHeader=null, weil er sonst eh nur stört.)

André, ich möchte dich nochmal was fragen.

Ich habe in deiner Example-Klasse folgende Methode gefunden:

   aTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
   TableColumnModel tableColumnModel = aTable.getColumnModel();
   TableColumn tableColumn;
   for (int i = 0; i < aTable.getColumnCount(); i++) {
      tableColumn = tableColumnModel.getColumn(i);
      tableColumn.setPreferredWidth(columnWidth[i < columnWidth.length ? i : columnWidth.length - 1]);
   }
}```
Ich kann mit der Methoden-Signatur (Methoden-Kopf) nichts anfangen, sowas habe ich noch nie gesehen. :confused:
Was bedeutet **final int... columnWidth** ?

Außerdem habe ich verzweifelt versucht, einen zweizeiligen TableHeader zu konstruieren, bin aber bei mehreren Versuchen immer gescheitert. Ich habe ein funktionierendes Beispiel mit einer JList als Header-Komponente im Internet gefunden, das konnte ich zweizeilig darstellen. Lieber wäre mir eine Variante mit zwei JLabels auf einem JPanel.
Ich wollte auch mal das [GroupableHeaderExample](http://www.crionics.com/products/opensource/faq/swing_ex/JTableExamples1.html) ausprobieren, aber das funktioniert nicht.

Hast du was Funktionierendes?
  1. Die “…” sind sogenannte “Varargs”. Du kannst die Methode mit beliebig vielen int’s aufrufen. “resizeTable( table, 1, 2 )” und “resizeTable( table, 1, 2, 3, 4 )” sind z.B. beide möglich. Der Compiler macht daraus dann “resizeTable( table, new int[]{ 1, 2, 3, 4 } )”. Entsprechend verhält sich “int … x” dann in der Methode wie “int[] x”.

  2. Was den Header angeht: ich weiss nicht was André dir vorgeschlagen hat, persönlich würde ich versuchen die “column-view” des umschliessenden "JScrollPane"s durch was eigenes zu ersetzen. Etwa so:


import java.awt.*;
import java.util.ArrayList;
import java.util.List;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumnModel;

public class JTableTest{
    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 } );

        JTable table = new JTable( model ){
            @Override
            protected void configureEnclosingScrollPane() {
                super.configureEnclosingScrollPane();
                Container p = getParent();
                if (p instanceof JViewport) {
                    Container gp = p.getParent();
                    if (gp instanceof JScrollPane) {
                        JScrollPane scrollPane = (JScrollPane)gp;
                        scrollPane.setColumnHeaderView( new Header( this ) );
                    }
                }
            }
        };

        JFrame frame = new JFrame( "Demo" );

        JScrollPane pane = new JScrollPane( table );

        frame.getContentPane().add( pane );
        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        frame.pack();
        frame.setVisible( true );
    }

    private static class Header extends JPanel implements TableColumnModelListener{
        private TableColumnModel model;
        private List<JComponent> header = new ArrayList<JComponent>();

        public Header( JTable table ){
            model = table.getColumnModel();
            model.addColumnModelListener( this );

            for( int i = 0, n = model.getColumnCount(); i<n; i++ ){
                String title = String.valueOf( model.getColumn( i ).getHeaderValue() );
                JComponent component = new JButton( title );
                add( component );
                header.add( component );
            }

            setLayout( new LayoutManager(){
                public void addLayoutComponent( String name, Component comp ) {
                    // ignore
                }

                public void layoutContainer( Container parent ) {
                    int count = Math.min( header.size(), model.getColumnCount() );

                    int x = 0;
                    int height = getHeight();

                    for( int i = 0; i < count; i++ ){
                        int width = model.getColumn( i ).getWidth();
                        header.get( i ).setBounds( x, 0, width, height );
                        x += width;
                    }
                }

                public Dimension minimumLayoutSize( Container parent ) {
                    return preferredLayoutSize( parent );
                }

                public Dimension preferredLayoutSize( Container parent ) {
                    int height = 0;
                    for( JComponent component : header ){
                        height = Math.max( height, component.getPreferredSize().height );
                    }
                    return new Dimension( 1, height );
                }

                public void removeLayoutComponent( Component comp ) {
                    // ignore
                }
            });
        }

        public void columnAdded( TableColumnModelEvent e ) {
            // needs to be done...
        }

        public void columnMarginChanged( ChangeEvent e ) {
            // needs to be done...
        }

        public void columnMoved( TableColumnModelEvent e ) {
            // needs to be done...
        }

        public void columnRemoved( TableColumnModelEvent e ) {
            // needs to be done...
        }

        public void columnSelectionChanged( ListSelectionEvent e ) {
            // needs to be done...
        }
    }
}

Danke Beni, ich will’s versuchen. Kannst du den Code bitte noch etwas kommentieren? Dann verstehe ich das Ganze schneller und lerne dabei.

Oke, viel habe ich eigentlich nicht zu sagen :stumm:


import java.awt.*;
import java.util.ArrayList;
import java.util.List;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumnModel;

public class JTableTest{
    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 } );

        // Die Tabelle :-)
        JTable table = new JTable( model ){
            @Override
            protected void configureEnclosingScrollPane() {
                // Das umschliessende JScrollPane abändern...
                super.configureEnclosingScrollPane();
                Container p = getParent();
                if (p instanceof JViewport) {
                    Container gp = p.getParent();
                    if (gp instanceof JScrollPane) {
                        JScrollPane scrollPane = (JScrollPane)gp;
                        // ... und hier wird unser neuer Spalten-Header gesetzt
                        scrollPane.setColumnHeaderView( new Header( this ) );
                    }
                }
            }
        };

        JFrame frame = new JFrame( "Demo" );

        JScrollPane pane = new JScrollPane( table );

        frame.getContentPane().add( pane );
        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        frame.pack();
        frame.setVisible( true );
    }

    // Das ist unsere Implementation eines Headers
    private static class Header extends JPanel implements TableColumnModelListener{

        // welche Spalten es gibt
        private TableColumnModel model;
        // Componenten um die einzelnen Spalten zu beschriften
        private List<JComponent> header = new ArrayList<JComponent>();

        public Header( JTable table ){
            model = table.getColumnModel();
            model.addColumnModelListener( this );

            // Einfach mal alle Spalten nehmen und für jede einen JButton als Beschriftung einsetzen
            for( int i = 0, n = model.getColumnCount(); i<n; i++ ){
                String title = String.valueOf( model.getColumn( i ).getHeaderValue() );
                JComponent component = new JButton( title );
                add( component );
                header.add( component );
            }

            // Hier definieren wir, wie die Beschriftungen ausgerichtet werden.
            setLayout( new LayoutManager(){
                public void addLayoutComponent( String name, Component comp ) {
                    // ignore
                }

                public void layoutContainer( Container parent ) {
                    // Diese Methode wird aufgerufen wenn "validate", bzw. "doLayout" aufgerufen
                    // wurde. Dies ist das Herz eines jeden LayoutManagers.
                    int count = Math.min( header.size(), model.getColumnCount() );

                    int x = 0;
                    int height = getHeight();

                    for( int i = 0; i < count; i++ ){
                        int width = model.getColumn( i ).getWidth();
                        header.get( i ).setBounds( x, 0, width, height );
                        x += width;
                    }
                }

                public Dimension minimumLayoutSize( Container parent ) {
                    return preferredLayoutSize( parent );
                }

                public Dimension preferredLayoutSize( Container parent ) {
                    int height = 0;
                    for( JComponent component : header ){
                        height = Math.max( height, component.getPreferredSize().height );
                    }
                    return new Dimension( 1, height );
                }

                public void removeLayoutComponent( Component comp ) {
                    // ignore
                }
            });
        }

        // Wenn sich die Anzahl Spalten oder ihre Grösse ändert, sollten wir
        // darauf reagieren... aber dazu bin ich jetzt zu faul
        public void columnAdded( TableColumnModelEvent e ) {
            // needs to be done...
        }

        public void columnMarginChanged( ChangeEvent e ) {
            // needs to be done...
        }

        public void columnMoved( TableColumnModelEvent e ) {
            // needs to be done...
        }

        public void columnRemoved( TableColumnModelEvent e ) {
            // needs to be done...
        }

        public void columnSelectionChanged( ListSelectionEvent e ) {
            // needs to be done...
        }
    }
}

Dankeschön, mal sehen, was ich draus machen kann.

Ich habe nochmal eine Frage an André.
Warum bekomme ich beim Hinzufügen einer Zeile per addRow()-Methode zu einer MultiSpanCellTable mit AttributiveCellTableModel immer eine ArrayIndexOutOfBoundsException?
Einfach einen passenden Vector oder ein Object-Array mit der addRow()-Methode zum Model hinzuzufügen scheint offenbar nicht zu funktionieren.
Ich habe gesehen, dass du das Hinzufügen von Zeilen in deinem Beispiel durch ein immer wieder neu erzeugtes AttributiveCellTableModel und mit der setValueAt()-Methode erledigst. Machst du das, weil das gängige Praxis ist, oder weil du auch die Probleme beim Zeilen anfügen hattest?
Und wie wirkt sich das deiner Meinung nach auf die Performance beim Erstellen/Anzeigen der JTabel aus?

@Beni: Ich habe jetzt eine schöne Lösung mit einer JList gebastelt, die ist genau das, was ich mir vorstelle.
Ein JTableHeader mit JLabel ist aber auch möglich, man braucht nur die HTML-Funktionalität der Swing-Komponenten nutzen.

André hat mich per PN benachrichtigt, dass er momentan keine Zeit findet, sich dem Problem tiefergehend anzunehmen.
Daher habe ich jetzt ein TableModel welches von AbstractTableModel erbt geschrieben. Es implementiert alle benötigten Methoden (auch die Methoden zum Setzen der Zellattribute aus AttributiveCellTableModel) und ist funktional dem DefaultTableModel angelehnt.
Sobald es fertig ist, werde ich es mal hier zur Diskussion posten.

Langsam verliere ich die Lust an dieser MultiSpanCellTable. :mad:
Die dazu gehörende Klasse MultiSpanCellTableUI scheint einen Fehler zu haben, der die Zellen falsch zeichnet, wenn ich mein Model verwende.
Auch wenn ich der MultiSpanCellTable ein DefaultTableModel übergebe gibts Probleme… Scheint also alles nicht der Weisheit letzter Schluss zu sein.
Nur immer wieder ein neues AttributiveCellTableModel zu erstellen, kanns ja aber auch nicht sein.

Da meine hochgeladenen Dateien sich in Luft aufgelöst haben, versuch ich’s hier nochmal, auf Anfrage eines Benutzers.