GUI JTable mit mehreren TableModels + removeColumns

Halli Hallo

folgendes Problem bzw. Frage: Wie kann ich in einer JTable mit verschiedenen Models das Aussehen/Renderer auf alle Models anwenden?

Aktuell hab ich 2 Tabellen mit je zwei Models. Das erste Model ist jeweils Standard, wird also zuerst genutzt, und damit auch die Schrift, Größe, Farbe etc. festgelegt. Wechsel ich nun aber auf das andere Model per table.setModel(secondModel); dann klappt das auch, aber die Formatierung ist weg. Wechsel ich danach wieder zum Ausgangsmodel bleibt die “kaputte” Formatierung bestehen und er wechselt nicht wieder zurück.
Muss ich hier jedesmal beim Wechseln den Renderer neu setzen und mögliche Spalten ausblenden? Was mich zur nächsten Frage führt:

Wie blendet man Spalten korrekt aus? Mit table.removeColumn(...) kann man ja Spalten aus der View nehmen, kann sie aber noch abfragen da sie in den TableData noch drin steht. So ganz funktioniert das aber irgendwie nicht, bzw hab ichs noch nicht ganz geblickt. Jedesmal wenn ich da eine Spalte ausblende, scheinen sich die SPaltenID’s zu verschieben. Ich hab eine eigene TableRow gebaut, welche mit ID’s arbeitet. Also sowas wie model.getValueAt(1, TableModel.NAME);. Blende ich nun die erste Spalte aus table.removeColumn(table.columnModel().getCOlumn(TableModel.ID)); und will mir den Namen holen kommt nen CastError. Es sieht so aus als ob er dann die Namensspalte auf die falsche Spalte, die ID, parst. Und dann passts nicht mehr.
Das sollte doch aber eigtl. ohne Probleme gehen, oder irre ich mich. Denn das getValueAt() greift auch nur auf die eigene TableRow, per switch/case der übergebenen ID, zu.

Grüße

Entweder ich hab’ da was grundlegend falsch verstanden, oder … ähm… ein TableModel hat doch eigentlich (gerade) NICHTS mit Farbe, Schrift etc. zu tun ?! :confused:

Seh es grad, das das etwas unglücklich formuliert ist .P Die Formatierung selbst geht über die Tablle bzw. den Renderer. Beim ersten Start setz ich in jede Spalte meinen eigenen Renderer. Dann sieht die GUI auch aus wie gewollt. Schalt ich aber auf das zweite Model ist die Formatierung weg, weil die über table.getColumnModel().getColumn(i).setCellRenderer(myRenderer) läuft. Schalt ich wieder zurück zum Ausgangsmodel bleibt die „kaputte“ GUI bestehen

Oder anders gefragt: Wie krieg ich eine Tabelle, ohne Workarounds, zu einheitlichem Aussehen wenn ich zwischen Models wechsle :smiley:

€: Zum Beispiel sind ausgeblendete Spalten, aktuell noch mit ColumnWIdth(0), wieder sichtbar beim Modelwechsel

Klingt irgendwie recht kompliziert, spontane Gedanken wie etwa mehrere (gleich konfigurierte) JTables für die verschiedenen Models, die dann per CardLayout umgeschaltet werden, oder irgendwelche Hilfsmethoden wie configureAfterModelChange(table) sind wohl nicht, was du suchst. Vielleicht (!) würde ein KSKB helfen, aber vielleicht erstmal schaun ob jemand anderes spontane Gedanken dazu hat…

Das mit dem CardLayout und mehreren Tables wäre wohl möglich ja, aber dazu müsste man einiges umbauen. Denn aktuell sind es 4 verschiedene „Sichten“ welche einfach durch je ein TableModel repräsentiert werden. An eine Hilfsmethode hab ich auch schon gedacht, aber jedes mal die Tabelle zu aktualisieren erschien mir nicht wirklich performant.

Und irgendwie müsste das ja eigtl. auch intern geregelt sein. Wenn ich das Datenmodell austausche, das sich die View nicht ändert.

Ein KSKB müsst ich erst bauen. Aber im Pseudocode sieht es in etwa so aus:


t = new JTable();
t.setModel(myModelOne());

t.getColumn(1).setWidth(0);

for(Spalte s : t.getColumnModel)
s.setCellRenderer(myRenderer);

b= new JButton();
b.action(){
   wenn modelOne -> t.setModel(myModelTwo());
   wenn modelTwo -> t.setModel(myModelOne());
}

€: KSKB angehangen, sollte eigtl. ohne Fehler sein :slight_smile:

Hm. Über Performance würde ich mir da keine Gedanken machen, einen neuen Renderer zu setzen oder eine Spaltenbreite anzupassen dürfte im Mirkosekundenbereich liegen, und weniger Zeit benötigen, als z.B. den eigentlichen Inhalt der Tabelle dann zu rendern. Ansonsten … JTable ist AFAIK die mit Abstand komplexeste Klasse in Swing, und da kommt sicher einiges zusammen. Ob man z.B. sowas wie das Anpassen der Column Width schon durch einen geeigneten Aufruf von table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF) oder so verhindern kann, müßte man (ich) erstmal probieren, ebenso wie ob das Neusetzen des Renderers vielleicht durch sowas wie table.setAutoCreateColumnsFromModel(false) überflüssig wird. Ich denke aber, dass es eine einfache, nachvollziehbare Lösung geben müßte, die ohne solche “tweaks” (wenn man es denn so nennen will) auskommt.

[quote=MannOhneNick]Das mit dem CardLayout und mehreren Tables wäre wohl möglich ja, aber dazu müsste man einiges umbauen.[/quote]Weil das sooooo aufwändig ist habe ich das mal für Dich vorbereitet:```public class MainTable {
public static void main(String[] args) {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final Model model1 = new Model();
final Model model2 = new Model();
Row row1 = new Row(“test1_1”);
Row row2 = new Row(“test1_2”);
Row row3 = new Row(“test1_3”);
Row row4 = new Row(“test2_1”);
Row row5 = new Row(“test2_1”);

	model1.addRow(row1);
	model1.addRow(row2);
	model1.addRow(row3);
	model2.addRow(row4);
	model2.addRow(row5);

	final CardLayout cardLayout = new CardLayout();
	final JPanel cardPanel = new JPanel(cardLayout);
	cardPanel.add(prepareTablePane(new JTable(model1)), "0");
	cardPanel.add(prepareTablePane(new JTable(model2)), "1");

	f.add(cardPanel, BorderLayout.NORTH);
	JButton b = new JButton("umschalten");
	b.addActionListener(new ActionListener() {
		@Override
		public void actionPerformed(ActionEvent e) {
			cardLayout.next(cardPanel);
		}

	});
	f.add(b, BorderLayout.SOUTH);
	f.pack();
	f.setVisible(true);
}

private static JComponent prepareTablePane(final JTable table) {
	MyRender r = new MyRender();

	table.setAutoCreateRowSorter(true);

	table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
	table.setDoubleBuffered(true);
	table.setRowSelectionAllowed(true);
	table.setShowGrid(true);
	table.setFillsViewportHeight(true);
	table.setDragEnabled(false);
	table.setGridColor(Color.BLACK);
	table.setSelectionForeground(Color.WHITE);
	table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
	table.setAutoCreateRowSorter(true);
	for (int i = 0; i < table.getColumnModel().getColumnCount(); i++) {
		table.getColumnModel().getColumn(i).setCellRenderer(r);
	}

	JScrollPane pane = new JScrollPane(table);
	pane.setOpaque(true);
	JPanel panel = new JPanel(new BorderLayout());
	panel.add(pane);
	return panel;
}```

Weitere Anregung findest Du
-Byte-Welt-Wiki - LayoutManager
-http://docs.oracle.com/javase/tutorial/uiswing/layout/card.htm.

bye
TT

Ich weiß wohl wie man das CardLayout benutzt, danke. Dennoch ist es aktuell erstmal keine Option bzw. steht als Lösung relativ weit unten. Denn die Table ist in einem etwas komplexeren Gebilde eingebettet als in dem Beispiel welches ich gespostet habe

Aber so wie ich das nachvollziehen kann ist das Table Element auch nach Schema F Model<->View aufgebaut. Also sollte es doch eigtl. möglich sein das Model zu verändern, die Sicht aber bestehen bleibt. Oder nicht? Irgendwie erscheint es mir nicht ganz nachvollziehbar das die Lösung für das Problem ist, x-Tables zu erzeugen und diese im CardLayout anzuordnen. Da muss es doch was “eleganteres” geben

[quote=MannOhneNick]Dennoch ist es aktuell erstmal keine Option[/quote]Das lese ich so:

Ich lasse mir meinen Superduperansatz mit dem Tausch der Models nicht kaputt reden und will eine dazu passende Lösung egal wie aufwändig die wird…

Viel Glück!

bye
TT

Dann ist dein Leseverständnis nicht gerade ausgeprägt, sonst würdest du den Zusatz danach auch noch lesen :slight_smile:

Dennoch hab ich halt gedacht das eine elegante Methode gibt verschiedene Models in einer Table anzuzeigen und relativ einfach durchzuswitchen.

Die gibt es, durch setModel. Dass dabei bestimmte Dinge sich ändern, von denen du gerade willst, dass sie sich NICHT ändern, ist halt Pech. Durch das Ändern des Modells ändert sich SO viel, dass es vermutlich kaum Sinn machen würde, auseinanderzupflücken, was gleich bleiben kann und was nicht - etwa ob der Renderer, der für Column 5 gesetzt wurde, gesetzt bleiben kann oder sollte, obwohl sich der Typ der Elemente in Spalte 5 von String auf Integer geändert hat, oder das Modell vielleicht sogar nur noch 4 Spalten hat…

Die von Timothy_Truckle angedeutete Spekulation über die Gründe, warum es “keine Option” ist, das anders zu machen, ist vielleicht nicht gerechtfertigt. Für mich persönlich klingt (!) das bisher nicht nach “Ich finde die Idee so toll, dass ich es unbedingt machen will”, sondern eher nach “Da hat irgendjemand so viel Rotz zusammengestümpert, dass ich möglichst wenig daran ändern will, aber trotzdem eine neue Funktionalität da sein soll” (klassischer Lava Flow).

Hast du die genannten Methoden ausprobiert? (KSKB ist bei Lava Flow Situationen schwierig, ich weiß…)

Kannst du nicht die vorgenommenen Änderungen am bisherigen Modell chronologisch speichern, und nach einem Modellwechsel einfach wiederholen?

Wenn man sich den Code von setModel anschaut, stellt man fest, dass normalerweise die Methode createDefaultColumnsFromModel() aufgerufen wird. Die Implementierung von JTable löscht alle Columns aus dem ColumnModel und erstellt neue, du könntest also eine Subklasse von JTabel erstellen die diese Methode überschreibt und die Columns nur verschiebt/aus einem Pool entnimmt. Alternativ kannst du (wie schon von Marco vergeschlagen) table.setAutoCreateColumnsFromModel(false) setzten, damit die Methode nicht aufgerufen wird, ergo auch keine Veränderung am ColumnModel durchgeführt werden.
Einen anderen Weg sehe ich nicht, wenn du nicht jedes Mal die Renderer neu setzen willst.

Nach hin und her probieren hab ich es dann doch mit dem CardLayout gemacht. Paar Kleinigkeiten sind noch nicht ganz ok, aber das wird schon. Danke nochmal an alle.

Bleibt noch eine Frage. Aktuell wird immer das gleiche TableModel erzeugt und der Tabelle hinzugefügt. Doch eine Tabelle braucht davon nicht alle 6 Spalten, sondern nur 4. Also hab ich deren Spalte einfach auf 0 gesetzt. Soweit so gut. Irgendwo hab ich aber nun gelesen das man die auch ganz aus der View löschen kann, sie aber im Model bleibt, per removeColumn. Auch das geht soweit, nur kommt dann der CellRenderer durcheinander weil die ColumnID’s ja nicht mehr stimmen und kann manche Objekte nicht mehr parsen.

Strukturiert ist das TableModel in der Form das es ID’s für jede Spalte gibt, die man mit MyTableModel.ID zb abfragen kann. Entferne ich die 1. Spalte aber, ist u.U. aber ein anderes Objekt in der 1. Spalte. So, dass der Renderer bei if(column = MyTableModel.ID)... zwar die 1. Spalte meint, darin aber nicht mehr die ID steht sondern was ganz anderes

Gibts da nen Trick/Vorgehen wie man das löst oder ist das auch so ne Sache die eigtl. nicht vorgesehen ist? Mit der Spaltenbreite gleich 0 läufts auch, daher ist das eigtl. eher so eine Verständnisfrage :slight_smile:

[QUOTE=MannOhneNick]
Bleibt noch eine Frage. Aktuell wird immer das gleiche TableModel erzeugt und der Tabelle hinzugefügt. Doch eine Tabelle braucht davon nicht alle 6 Spalten, sondern nur 4. Also hab ich deren Spalte einfach auf 0 gesetzt. [/QUOTE]

Wenn du nur 4 Spalten brauchst, warum erzeugst du dann 6?

Ich hatte zwar irgendwann mal ein “RangeTableModel” geschrieben, mit dem man quasi einen Teil eines anderen TableModels wieder als eigenes TableModel ansehen konnte, aber ich vermute, dass das hier nicht die geeignet(st)e Lösung wäre…

Faulheit. So konnte man das einmal erzeugte Model nutzen. Die Spalten ID’s bleiben gleich, u.U. ist die Info vorhanden welche man vllt später nochmal brauchte. Hauptgrund waren aber eigtl. die ID’s. Man konnte überall mit MyModel.ID arbeiten und musste nicht darauf achten ob man nun MyModel2.ID oder MyModel1.ID benutzen muss

[quote=MannOhneNick]Gibts da nen Trick/Vorgehen wie man das löst[/quote]Wenn man seine “Primitive Obsession” überwunden hat kann man für jede Spalte des TableModels eine eigene Wrapper Klasse schreiben, zu mindest für die, die keine Standard-Renderer verwenden können/sollen. Dann kann man für jede Modell-Spalte einen entsprechenden Renderer registrieren, egal ob die Spalte dann tatsächlich in der Tabelle drin ist oder nicht. Das Model mus dann nur in der Methode getColumnClass(int column) die passende Klasse zurückgeben.

bye
TT

Vielleicht sind die Methoden 4 Methoden auf http://docs.oracle.com/javase/7/docs/api/javax/swing/JTable.html#convertColumnIndexToModel(int) was für dich. Damit kannst du die Indizes zwischen Model und View umwandeln.

ich hab da mal was zum Spielen: [SPOILER]```public class TableRendererTest {

// Die Interfaces werden nur für die Bestimmung den CellRenderers gebraucht und müssen nirgens implementiert werden.
public interface BlueTag {}
public interface RedTag {}
public interface YellowTag {}
public interface GreenTag {}

// der Zennrendeer wird für die verschiedenen Interfaces undterschiedlich konfiguriert
private static final class DefaultTableCellRendererExtension extends
		DefaultTableCellRenderer {
	private final Color backGround;
	private DefaultTableCellRendererExtension(Color backGround) {
		this.backGround = backGround;
	}

	@Override
	public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
		Component rendererComponent = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
		
		// farbe Abhängig von SpaltenTyp
		rendererComponent.setBackground(backGround);
		
		// Rand abhängig von SpaltenIndex
		((JComponent) rendererComponent).setBorder(BorderFactory.createLineBorder(backGround.darker().darker(), column));
		return rendererComponent;
	}
}
private static final TableModel baseModel = createBaseModel();

public static void main(String[] args) {
	JTable jTable = new JTable(baseModel);
	setRenderer(jTable);
	JPanel jPanel = prepareDisplay(createTableModels(), jTable);
	JOptionPane.showMessageDialog(null, jPanel);
}

private static JPanel prepareDisplay(final List<TableModel> modelList,
		final JTable jTable) {
	JPanel jPanel = new JPanel(new BorderLayout());
	
	jPanel.add(jTable);

	jPanel.add(new JButton(new AbstractAction("voriges Model") {
		@Override
		public void actionPerformed(ActionEvent e) {
			int indexOfModel = modelList.indexOf(jTable.getModel());
			if (indexOfModel < 1)
				indexOfModel = modelList.size();
			jTable.setModel(modelList.get(indexOfModel - 1));
		}
	}), BorderLayout.NORTH);
	
	jPanel.add(new JButton(new AbstractAction("nächstes Model") {
		@Override
		public void actionPerformed(ActionEvent e) {
			int indexOfModel = modelList.indexOf(jTable.getModel());
			if (indexOfModel + 1 == modelList.size())
				indexOfModel = -1;
			jTable.setModel(modelList.get(indexOfModel + 1));
		}
	}), BorderLayout.SOUTH);
	return jPanel;
}

private static void setRenderer(final JTable jTable) {
	jTable.setDefaultRenderer(BlueTag.class, new DefaultTableCellRendererExtension(Color.BLUE));
	jTable.setDefaultRenderer(YellowTag.class, new DefaultTableCellRendererExtension(Color.YELLOW));
	jTable.setDefaultRenderer(RedTag.class, new DefaultTableCellRendererExtension(Color.RED));
	jTable.setDefaultRenderer(GreenTag.class, new DefaultTableCellRendererExtension(Color.GREEN));
}

private static List<TableModel> createTableModels() {
	final List<TableModel> modelList = new ArrayList<>();
	modelList.add(createSampleModel(
			baseModel,
			Arrays.asList(Object.class, RedTag.class,
					GreenTag.class, BlueTag.class,
					YellowTag.class).toArray(new Class<?>[0])));
	modelList.add(createSampleModel(
			baseModel,
			Arrays.asList(YellowTag.class, RedTag.class).toArray(
					new Class<?>[0])));
	modelList.add(createSampleModel(
			baseModel,
			Arrays.asList(GreenTag.class, RedTag.class,
					BlueTag.class).toArray(new Class<?>[0])));
	modelList.add(createSampleModel(
			baseModel,
			Arrays.asList(GreenTag.class, BlueTag.class,
					Object.class, RedTag.class,
					YellowTag.class).toArray(new Class<?>[0])));
	return modelList;
}

private static TableModel createSampleModel(final TableModel baseModel,
		final Class<?>[] columnClasses) {
	TableModel model1 = new DefaultTableModel(15, columnClasses.length) {

		@Override
		public Class<?> getColumnClass(int columnIndex) {
			return columnClasses[columnIndex];
		}

		@Override
		public Object getValueAt(int row, int column) {
			return baseModel.getValueAt(row, column % columnClasses.length
					+ baseModel.getColumnCount() / columnClasses.length);
		}

	};
	return model1;
}

private static TableModel createBaseModel() {
	TableModel baseModel = new DefaultTableModel(15, 5) {

		@Override
		public Object getValueAt(int row, int column) {
			NumberFormat integerInstance = NumberFormat
					.getIntegerInstance();
			integerInstance.setMinimumIntegerDigits(2);
			return MessageFormat.format("{0} - {1}",
					integerInstance.format(row),
					integerInstance.format(column));
		}
	};
	return baseModel;
}

}```[/SPOILER]
bye
TT