JTable mit fixer ColumnWidth

So und wieder ein tolles JTable-Problem…

Meine JTable wird mit 0 Spalten/Zeilen initialisiert. Bei Knopfdruck werden dann sowohl neue Spalte als auch Zeile zugefügt oder wieder entfernt. Nun möchte ich aber (weil ich das alles im TableModel mache) nicht dabei noch dauernd meiner JTable sagen dass die neu erstellte Spalte eine bestimmte Breite haben soll. Gibt es eine Möglichkeit einzustellen, dass jede neu erzeugte Spalte eine gewisse Breite hat?

Leider finde ich nur tausende Threads wie ich die Spaltenbreite nachträglich ändere, aber genau das will ich ja nicht.
Grüße, TheAceOfSpades

Das sollte eigentlich nur eine/zwei zusätzliche Zeile/n Code bedeuten. Wie fügst Du eine neue Spalte hinzu? Nutzt Du ein DefaultTableModel und dessen Methoden oder ein eigenes?

gegen

    /** 
     *  Cover method, using a default width of 75, a <code>null</code>
     *  renderer and a <code>null</code> editor. 
     *  @see #TableColumn(int, int, TableCellRenderer, TableCellEditor)
     */
    public TableColumn(int modelIndex) {
	this(modelIndex, 75, null, null);
    }

ist wohl kein Kraut gewachsen,
da helfen nur diverse Eigenimplementationen

z.B. unschönerweise im Model die JTable ergänzen und doch noch selber nachträglich JTable ändern,
immerhin auf möglichst allgemeine Weise, nicht für einzelne Tabellen immer Code nötig,
maximal darauf zu achen ‘TableModelWithSpecialDefaultColumnWidth’ zu verwenden und da natürlich auch die DefaultWidth anzugeben

oder an anderen Stellen im Prozess, JTable überschreiben, einen Listener adden,
ich gehe davon aus dass du selber schon Ideen hast, bleibe zunächst bei nüchterner Absage (meiner Vermutung nach)

public class RatingTableModel extends DefaultTableModel{
	...

public void addCrit(String crit){
		crits.add(crit);
		
		setColumnIdentifiers(crits);
		addRow(new Vector());
		
		int cols = getColumnCount() - 1;
		super.setValueAt(1, cols, cols);
		
	}

...

}

Im TableModel kann ich da ja aber recht wenig machen, das beinhaltet doch nur die Daten oder? Wenn, dann müsste ich doch dann das ColumnModel meiner JTable erweitern, oder?

der gepostete Code sorgt für sich doch kaum für eine neue Column, oder?
falls die JTable als Attribut vorhanden ist (was freilich ‘1-2 Codezeilen’ für sich schon strapazieren würde)
kann an fraglicher Stelle nach dem Event evtl. auf die JTable und anderes zugegriffen werden

ColumnModel ist ein anderer Angriffspunkt, richtig

Das ist richtig. Aber das DefaultTableModel bietet die Methode addColumn mit der man bequem neue Spalten ergänzen kann. Würdest Du ein eigenes TableModel verwenden müsstest Du Dich selbst um diese Erweiterung kümmern und würdest ggf. explizit eine neue TableColumn erzeugen, der man dann auch eine feste Breite zuweisen könnte. Daher die Frage.

Verwendest Du da absichtlich addRow, dachte es geht darum neue Spalten einzufügen?

Was spricht dagegen sich im Worst Case die TableColumn über das ColumnModel zu holen und daran z.B. Min und MaxWidth zu setzen?

Ok, also etwas ausführlicher:

Ich habe eine Klasse RatingTable, die momentan noch nicht viel macht:

public class RatingTable extends JTable {
	
	private static final long serialVersionUID = 1L;
	
	private RatingTableModel rModel;
	
	public RatingTable(){
		
		//Das TableModel setzen
		rModel = new RatingTableModel();		
		setModel(rModel);
		
		//Zellengröße festlegen:
		setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
		setRowHeight(40);
		
	}
	
	public RatingTableModel getModel(){
		return rModel;
	}
}

Den relevanten Ausschnitt des RatingTableModels habe ich oben schon gepostet.

Die Tabelle wird in einem Panel erzeugt und in ein JScrollPane gebettet:

//Tabelle in JScrollPane
		critTable = new RatingTable();
		JScrollPane pane = new JScrollPane(critTable);
		pane.setRowHeader( new RatingTableRowHeader( critTable, pane ) );

...

critPanel.add(pane, "0,0,0,1");

In diesem Panel liegt auch ein JButton, der folgendes ActionEvent auslöst:

public void actionPerformed(ActionEvent ae) {
		if(ae.getActionCommand().equals("add")){
			Object crit = critLists.getPlusList().getSelectedValue();
			//Wenn nichts angewählt ist soll (wenn möglich) das erste Kriterium gewählt werden
			if(crit==null){
				try{
					crit = critLists.getPlusList().getModel().getElementAt(0);				}
				catch(ArrayIndexOutOfBoundsException e){
					return;
				}
			}
			
			critLists.addCrit(crit);
			critTable.getModel().addCrit(crit.toString());
		}

Das hinzufügen der Spalte passiert in der letzten Codezeile.

Ich hoffe jetzt ist klar was passiert :slight_smile:

Und was spricht dann dagegen hier direkt im Anschluss die Spaltenbreite zu setzen?

critTable.getModel().addCrit(crit.toString());
TableColumn column = critTable.getColumnModel().getColumn(critTable.getColumnCount()-1);
column.set...```

Das habe ich mir direkt nach dem Post auch gedacht, etwas wurmt mich aber noch. Das ganze habe ich geändert zu

//Im ActionListener
if(ae.getActionCommand().equals("add")){
			...
			critTable.addCrit(crit.toString());
		}

...
//In RatingTable
public void addCrit(String crit){
		rModel.addCrit(crit);
		
		getColumnModel().getColumn(getColumnCount()-1).setPreferredWidth(120);
	}

Dann hat aber immer nur die letzte Spalte die PreferredWidth, der Rest wird bei zufügen wieder „zurückskaliert“. Nun könnte ich das natürlich mit einer Schleife lösen, aber das empfinde ich jetzt erstmal als unsauber, vor allem weil in der Tabelle später mehrere 100 Spalten drinstehen können und ich es als unnötig sehe jedes Mal wenn er eine Spalte hinzufügt alle mit neuer Breite versehen zu müssen. Gehen würde es ja schon, von Performance muss man an der Stelle glaube ich nicht anfangen. Aber ich frage hier ja nach weil ich das ja so eigentlich nicht haben will :stuck_out_tongue:

bei den 100 Colums wirst du aber wohl nicht direkt die Wahl haben,
das setColumnIdentifiers() führt letztlich zu fireTableStructureChanged();

deswegen wird die neue Column erkannt, dabei aber kompletter Neuaufbau,
100x Size-Setzen ist das hinsichtlich Performance noch das geringste Problem:
egal ob du die Default-Size magst oder nicht werden alle Columns neu erzeugt

daran reparieren kannst du genauso, bietet sich im Verbund mit dem anderen Problem umso mehr an,

wieviele andere Wege, ergo Events, es im DefaultTableModel bzw. AbstractTableModel gibt, weiß ich nicht genau,
vielleicht auf dessen Unterstützung verzichten,
selber Daten und Columns verwalten, dann auch von Array oder Vector befreit,
beim Hinzufügen einer Spalte auf humanere Weise das ColumnModel der JTable erreichen (welches vielleicht deine Klasse ist)
dort nur eine Column hinzufügen und dann auch die richtige Size setzen

Column-Änderungen also selber managen, für neue Zeilen/ Datenänderungen durchaus fireTableDataChanged() von AbstractTableModel oder genaueres,
evtl. auch von DefaultTableModel erben oder kopieren:

    public void setValueAt(Object aValue, int row, int column) {
        Vector rowVector = (Vector)dataVector.elementAt(row);
        rowVector.setElementAt(aValue, column);
        fireTableCellUpdated(row, column);
    }

oder wenn das alles zuviel ist, da kann man auch manches falsch machen und dann selber Performance kaputtoptimieren statt zu verbessern,
dann mit den 100 neuen Columns leben

Also von dem Buchstabenwald in der Mitte habe ich vielleicht die Hälfte verstanden, aber ich versuch mal drauf zu antworten in der Hoffnung dass ich es richtig interpretiere:

Das mit der Performance lasse ich einfach mal so stehen, darum geht es erstmal nicht.

Zuerst hatte in der addCrit() Methode ein einfaches addColumn(crit) stehen. Aber genauso wie ich addCrit() habe, habe ich auch removeCrit():

public void removeCrit(String crit){
		Vector dataVec = getDataVector();
		
		for(int c=0; c<crits.size(); c++){
			if(crits.get(c).equals(crit)){
				//Kriterium aus Überschriften entfernen
				crits.remove(c);
				
				//Tabelleninhalt aktualisieren
				for(int v = 0; v  < dataVec.size(); v++)
					((Vector) dataVec.get(v)).remove(c);				
				dataVec.remove(c);
				
				
				//removeRow(c);
				break;
			}
		}
		
		setDataVector(dataVec, crits);
		setColumnIdentifiers(crits);
	}

Hierbei muss ich eine Spalte sowie eine Zeile löschen. Leider habe ich keine andere Möglichkeit gefunden, aus dem Model einfach eine Spalte zu entfernen. Wenn ich sie aber so entferne, dann muss ich sie mit setColumnIdentifierts(crits) wieder hinzufügen, mit addColumn(crit) habe ich die Spalte dann zweimal.

Aber mir leuchtet ein dass auf dem aktuellen Weg sowieso alle Spalten neu eingesetzt werden…

Wie SlaterB schon erwähnt hat haben solchen Aktionen beim DefaultTableModel eine sehr tiefgreifende Wirkung. Aus eigener Erfahrung ist es empfehlens Wert sein eigenes TableModel abgeleitet vom Interface oder AbstractTableModel zu definieren.

Alternativ könnte man versuchen über das ColumnModel per addColumn(TableColumn) die neue Spalte hinzuzufügen, das wirkt nicht so extrem. Es kann aber durchaus berechtigt sein, dass das DefaultTableModel einen kompletten Neuaufbau auslöst.

Wenn man kein eigenes TableModel implementieren will, ist es denke ich das einfachste bei jedem Hinzufügen/Entfernen per Schleife die Spaltenbreiten zu setzen.

@TheAceOfSpades
addColumn() (edit: im DefaultTableModel) führt auch zu fireTableStructureChanged(), in der Hinsicht nichts zu gewinnen,

addColumn() fügt nebenbei auch im Datenvector für alle Zeilen einen Eintrag ein,
das muss man bedenken, bei dir aber wohl kein angesprochenes Problem

zum angegebenen Code ist noch zu sagen dass zwei gleiche Spalten hintereinander nicht gefunden werden würden,
steht c etwa bei 3, wird die Spalte in Index 3 entfernt, rückt die nächste Spalte auf Position 3 auf,
in der nächsten Runde wäre eigentlich wieder Index 3 zu prüfen, c wird aber auf 4 erhöht,

allgemein gefährliches Schleifenkonstrukt,
falls hier aber jede Spalte eh nur einmal vorhanden sein, dann kein akutes Problem,
edit: ach ja, ein break auch dabei, macht es klar


eine Frage ist aktuell nicht offen, oder?
abschweifen (auch wenn von anderen verursacht :wink: ) immer gefährlich,
ruhig zum Thema erinnern, unschön über viele Postings zurückschauen zu müssen ob noch irgendwas offen ist

Also generell würde ich gerne bei DefaultTableModel bleiben. Nicht zuletzt deshalb, weil ich ja andernfalls die eingegeben Daten manuell verwalten müsste (Wie SlaterB ja glaube ich schon gesagt hat), und das ganze würde mir dann wahrscheinlich zu unübersichtlich.

Das Schleifenkonstrukt funktioniert so, die Liste mit möglichen Spaltennamen gebe ich vor ;).


Die Frage an sich war eigentlich, ob es eine Möglichkeit gibt, dass jede neu erzeugte Spalte eine vorher festgelegte Breite hat. Also quasi so etwas wie setRowHeight(), nur eben für Spalten. Die Hoffnung verliere ich aber so langsam :stuck_out_tongue:

Es gibt nicht nur ein TableModel sondern auch ein TableColumnModel. Da kannst du die addColumn Methode überschreibst und deine TableColumn die du addest so veränderst wie du willst.
http://docs.oracle.com/javase/7/docs/api/javax/swing/table/DefaultTableColumnModel.html