Swing setPreferredWidth für JTable funktioniert nur beim ersten Aufruf!?

kleine247

Mitglied
Hallo,

ich entschuldige mich im Voraus, wenn die Antwort auf meine Frage hier irgendwo im Forum zu finden ist und ich sie nicht gefunden/verstanden habe. Ich habe sehr wohl gesehen, dass hier viele ähnliche Beiträge sind, aber irgendwie hat mich noch nichts weitergebracht.

Ich versuche gerade eine schöne Oberfläche auf die Reihe zu kriegen, wobei ich mehrere verschachtelte Panels habe. In einem JScrollPane habe ich ein JTable und habe es jetzt endlich geschafft, dass die Spaltenbreiten so gesetzt werden wie ich will (es wird erst jede Spalte so breit gemacht wie minimal notwendig um allen Text zu sehen und wenn das Panel dann noch Platz hat, werden die Breiten gleichmäßig erhöht). Die Methode, die das macht, wird direkt am Anfang aufgerufen und das klappt auch. Damit die Spaltenbreite auch wieder angepasst wird, wenn der Benutzer die Größe des Gesamtfensters ändert, habe ich einen HierarchyBoundsListener() hinzugefügt, der das ganze dann nochmal aufruft (und das tut er auch)... nur leider ändert sich dadurch an den Spaltenbreiten dann absolut gar nichts >.< Mein col.setPreferredWidth wird aber mit neuen Zahlen aufgerufen. Mit col.setMinWidth und col.setMaxWidth ändert sich leider auch nichts. Außerdem habe ich hintendran noch ein validate auf das JSplitPane gemacht, in dem mein JScrollPane mit der JTable ist. Jetzt fällt mir aber langsam nichts mehr ein. Fehlt da noch irgendwas ganz Offensichtliches?

Wenn das so nicht nachvollziehbar ist, kann ich gerne auch Code posten.
 
Zuletzt bearbeitet von einem Moderator:
Warum setzt du die Spaltenbreite nicht in Abhängigkeit von der darüber liegenden Panel-Breite? einfacher isses allemal also sich "ranzutasten".

An sich sollte die Tabelle mit der GUI rendern - wenn sie denn relativ bemessen ist und kein minWidth()/ setmaxWidth() gesetzt ist.
 
genau das versuche ich ja. mein setPreferredSize soll jedes Mal, wenn die Gesamtgröße geändert wird, in Abhängigkeit vom Panel gesetzt werden
 
Hallo,

leider besteht mein Problem immer noch. Nur ist mir klar geworden, dass das Problem nicht nur im graphischen Bereich liegt. Ich schaffe es einfach nicht, dass meine Tabelle irgendwie aktualisiert wird.

Mit der Methode 'showResult' erzeuge ich eine neue Tabelle und mache die ganzen Berechnungen für die richtige Breite der Spalten. Die Methode wird direkt im Konstruktor aufgerufen und beim ersten Anzeigen sind auch alle Spaltenbreiten so wie sie sein sollen. Wenn sich jetzt die Daten in der zugrundeliegenden MySQL Datenbank ändern, soll das natürlich sichtbar werden. Über einen aktualisieren-Button rufe ich deswegen 'redrawTable' auf, was wiederrum nichts anderes macht, als wieder 'showResult' aufzurufen. Die Daten sollen also komplett neu gelesen werden und die Tabelle neu aufgebaut. Ich mache im Prinzip nichts anderes, wie beim ersten Mal auch. Deswegen komme ich auch einfach nicht dahinter, warum sich danach in meiner Tabelle rein gar nichts ändert (nachdem ich in der DB Daten hinzugefügt habe).
Ich muss irgendwo einen offensichtlichen Denkfehler oder eine totale Wissenslücke haben, von der ich noch nichts weiß 😵

Hier jetzt mal mein Code, wobei ich denke, dass ihr euch nicht die Mühe machen müsst, meinen ganzen Berechnungskram 100% nachzuvollziehen, da dieser laut meinen Probeausgaben funktioniert. Es wird nur einfach nicht angezeigt =(
Java:
public class CentreView extends JSplitPane {

    JPanel panelFunctions;
    JScrollPane paneResults;	
    JTable table=null;
    ...

    //Konstruktor
    public CentreView (MainView mainView){	
        ...
        showResult (activeAccount.getResult(), activeAccount.getAttribute());
        ...
        setTopComponent(panelFunctions=new JPanel());
        setBottomComponent(paneResults);
    }

    //wird aufgerufen bei resize oder bei Klick auf aktualisieren (Tabelle komplett neu erzeugen)
    public void redrawTable (){		
        this.table=null;
        showResult (activeAccount.getResult(), activeAccount.getAttribute());		
    }

    //Result-Tabelle mit Daten aus MySQL DB erstellen und in Panel anzeigen
    public void showResult(ResultSet result, Vector<String> attributes){ 
        Vector<Vector<Object>> tableRows= new Vector<Vector<Object>>(); 	
        //Datenstruktur aufbauen, die von JTable Konstruktor akzeptiert wird    
        try {
            while (result.next()) {			//jede Zeile				
                Vector<Object> row = new Vector<Object>();
                for (String s: attributes){	//jedes Attribut
                    row.add(result.getObject(s));	        		
                }
                tableRows.add(row);
            }
        }catch (Exception e){
            System.out.println("Fehler bei Tabellenbefüllung "+e);
        }
        //JTable erzeugen
        table = new JTable(tableRows, attributes);		
        //Spalten nicht mehr gleichmäßig über die verfügbare Fläche verteilen, damit eigene Berechnungen gelten
        table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); 
				
        DefaultTableColumnModel colModel = (DefaultTableColumnModel)table.getColumnModel();
				
        //eigene Methode aufrufen, um für jede Spalte der Tabelle, die minimal benötigte Spaltenbreite zu ermitteln
        List<Integer> necessaryWidths = getNecessaryWidth(table, colModel);
        //insgesamt benötigte Breite für Tabelle erreichnen
        int necessaryWidthSum=0;
        for (Integer i: necessaryWidths){
            necessaryWidthSum+=i;
        } 
				
        //Tabelle dem JScrollPane hinzufügen
        paneResults= new JScrollPane(table);
		
        //verfügbare Breite für Tabelle ermitteln
        int availableWidth=0;
        //Höhe zur Verfügung kleiner als Höhe benötigt	
        if (paneResults.getPreferredSize().height<table.getPreferredSize().getHeight()) {	
            //Gesamtframebreite - Menübreite - Puffer, damit kein Scrollen - Platz für Scrollbar
            availableWidth=(int)mainView.getSize().getWidth()-(int)mainView.getPanelMenue().getPreferredSize().getWidth()-20-15;
        }else {
            //Gesamtframebreite - Menübreite - Puffer, damit kein Scrollen 
            availableWidth=(int)mainView.getSize().getWidth()-(int)mainView.getPanelMenue().getPreferredSize().getWidth()-20;
        }
		
        //ermitteln, um wieviel die Tabellenspalten ggf noch erhöht werden können und Spaltenbreiten setzen
        int add =0;
        int remainder=0;
        if (availableWidth>necessaryWidthSum){			
            add = (int) ((availableWidth-necessaryWidthSum) / table.getColumnCount());
            remainder = (int) (availableWidth - necessaryWidthSum - (add*table.getColumnCount()));
						
            for (int i = 0; i<table.getColumnCount(); i++){
                TableColumn col = colModel.getColumn(i);
                if (i==table.getColumnCount()-1){	//letzte Spalte kriegt den Rest
                    col.setPreferredWidth(necessaryWidths.get(i)+add+remainder);
                }
                else{
                    col.setPreferredWidth(necessaryWidths.get(i)+add);	
                }
            }	
        }		
        this.validate();	
		
        paneResults.setVisible(true);	
        table.setVisible(true);	
    }
}
 
Deine Berechnungen haben mit deinem Problem überhauptnichts zu tun. Den wichtigen Code hast du nämlich rausgelöscht.
"Mit der Methode 'showResult' erzeuge ich eine neue Tabelle" da liegt dein Problem. Du erzeugst nicht eine neue Tabelle, du zeigst neue Werte an. Die Tabelle kann die gleiche bleiben. Wenn du eine Variable JTable table hast und diese null setzt, wird dein hat das keinerlei auswirkungen auf die GUI. Eine Objektvariable ist eine Referenz. Löscht du die Referenz, ist das Objekt immer noch da.

Wenn du einen JTable verändern willst, gib ihm mit setModel ein neues TableModel.
 
Danke für deine Antwort. Damit hab ichs schonmal versucht und aufgegeben. Nachdem ich jetzt von dir weiß, dass das der richtige Ansatz war, hab ichs nochmal probiert. Aber ich hab schon wieder tausende Varianten erfolglos ausprobiert. Ich weiß nich genau, ob ich richtig dahintersteige. Also ich hab jetzt folgendermaßen ein TableModel hinzugefügt:

Java:
table.setModel(		
		new AbstractTableModel() {
			private static final long serialVersionUID = 1L;
			public String getColumnName(int col) {
		        return attributes.get(col).toString();
		}
		public int getRowCount() { 
		    	return tableRows.size(); 
		}
		public int getColumnCount(){ 
		    	return attributes.size(); 
		}
		public Object getValueAt(int row, int col) {
		        return tableRows.get(row).get(col);
		}
		public boolean isCellEditable(int row, int col) { 	
		    	return true; 
		}
		public void setValueAt(Object value, int row, int col) {
		    	tableRows.get(row).set(col, value);
		        fireTableCellUpdated(row, col);
		}
});

Soo.. dann denk ich mir theoretisch, dass es sogar schon reichen müsste, zum Aktualisieren nur meinen verschachtelten Vector tableRows anzupassen. Okay, so einfach ist es aber anscheinend nicht.
Soweit ich gelesen habe, soll man also das TableModel anpassen, indem man die alten Zeilen entfernt und die neuen einfügt. Okay.. Aber das würde doch heißen, dass ich die Methode setValueAt(..) von meinem TableModel aufrufe und da wieder die Werte von tableRows reinschreibe, womit ich im Grunde doch gar nichts mache, weil ich Werte aus tableRows in tableRows schreibe. Das wäre nich so, wenn keine Referenz auf tableRows gespeichert ist. Okay, dann müsste mans wirklich anpassen - könnt ich noch irgendwie so nachvollziehen. Aber das krieg ich auch irgendwie nicht hin. Ich dachte mir, ich füg meinem TableModel noch eine Methode clear() hinzu, um erstmal die alten Zeilen zu löschen.

Java:
public void clear (){
	tableRows.clear();
}

Wenn ich dann aber table.getModel().clear(); aufrufen will, kennt er das clear nicht, weil ein normales TableModel das wohl nicht hat. Was macht man da? Oder andere Frage: Bewege ich mich annährend in die richtige Richtung, oder ist das alles Schrott, was ich mir hier überlege?
 
Hab das mit dem TableModel wahrscheinlich zu kompliziert gemacht. Ich dachte, so würde es jetzt ganz gut aussehen:

Java:
//tableRows und attributes mit aktuellen Daten befüllen
...

if (first){   //beim ersten Mal JTable und TableModel neu erstellen
	first=false;
	table = new JTable();	
	tabMod = new DefaultTableModel (tableRows, attributes);
	table.setModel (tabMod);
}	
else {	      //bei weiteren Durchgängen JTable und TableModel nur noch bearbeiten
	for (int i = 0; i<tabMod.getRowCount(); i++){   //Zeilen aus TableModel entfernen
		((DefaultTableModel) table.getModel()).removeRow(i);
	}
	for (int i = 0; i<tableRows.size(); i++){    //aktuelle Zeilen in TableModel einfügen
		((DefaultTableModel) table.getModel()).addRow(tableRows.get(i));
	}				
}

Leider lädt er dann so lange, bis es zu einem java heap space error kommt. Warum passiert das? Ich hab durch Probeausgaben herausgefunden, dass meine tableRows erstmal 17 Zeilen hat (stimmt mit der DB überein). Nachdem aber die erste for-Schleife im else Zweig durchgelaufen ist, in der ich die Zeilen aus dem TabelModel entferne, sind in tableRows nur noch 8 Zeilen!? Wie kommt denn sowas zustande - ich hätte gedacht das TableModel beeinflusst meine Daten entweder ganz oder gar nicht. Unabhängig davon, verstehe ich trotzdem nicht, warum er dann überfordert ist, weil er muss damit ja sozusagen weniger Zeilen durchgehen als geplant. Zur Info: Bei jedem Start wird noch ca. 5 Mal resize aufgerufen, was wahrscheinlich durch die ganzen verschachtelten Panels oder so passiert. Dadurch wird das ganze dann halt nicht nur einmal gerechnet - aber eine Erklärung ist das nicht..
 
Erstell einfach ein neues DefaultTableModel mit den Daten die du erhältst und weise dieses deiner JTable mit setModel zu.
 
Jaaa, jetzt hab ichs! Vielen Dank =)

Zusätzlich hatte ich noch den Fehler drin, dass

Java:
paneResults= new JScrollPane(table);

jedes Mal gemacht wurde. Darf natürlich nur beim ersten Mal sein.
 

Zurück
Oben