# getValueAt liefert "", aber es wird was angezeigt



## OlliL (30. Nov 2012)

Hallo,

ich habe mit meinen ComboBoxen in meiner Tabelle ein Problem.
Ich habe ein eigenes TableModel und einen eigenen CellEditor.

Ich habe einige Spalten, darunter 2 ComboBoxen.

Ich validiere nach dem Klick auf einen Button ob in den ComboBoxen Werte ausgewählt wurden (der 1. Wert der Combobox ist ein "" Wert). Ist dies  nicht der Fall, springe ich in die ComboBox, gehe in den Edit-Modus und verlasse diesen wieder um die Validierung inkl. Fehlermeldung in meinem CellEditor "anzuschmeissen".
Dabei beobachte ich nun folgendes:

In jeder Zeile ab der 2. Zeile wird in getValueAt in meinem TableModel beim "betreten" (table.editCellAt(row, realCol)) der ComboBox noch "" zurück gegeben. Dann wird der Editier-Vorgang beendet (table.getCellEditor().stopCellEditing()) und dann liefert aber 


```
ComboBoxItem editingValue = (ComboBoxItem) getCellEditorValue()
```

in meinem CellEditor.stopCellEditing() immer(!) den Wert aus der ComboBox meiner ersten Zeile zurück. Obwohl ich mich z.B. in Zeile 2 befinde. Das ist mit beiden meiner ComboBoxen so. Ich habe einen ähnlichen CellEditor für meine Text-Felder - dort habe ich das Problem nicht.

Wähle ich bewusst einen anderen Wert in der ComboBox aus, wird der ausgewählte Wert gesetzt. getCellEditorValue() liefert also nicht immer den Wert aus der 1. Zeile sondern nur in dem oben beschriebenen Fall.

Wie kann das sein? Was wird nach getValueAt noch ausgeführt was dieses Verhalten verursachen könnte? Wo könnte ich noch ein paar Breakpoints setzen?

Edit: es passiert auch, wenn ich der ComboBox den DefaultCellEditor zuweise. Es scheint also nicht am CellEditor zu liegen.


----------



## Landei (30. Nov 2012)

Wovon genau redest du? Die Java-API hat keine Klasse ComboBoxItem. 

Falls es doch Swing ist: Editoren und Renderer werden in JTable als eine Art Schablone verwendet und dauernd "recycled". Das Verhalten von JTable ist alles andere trivial (wie unzählige "HowTos" im Netz zeigen), also wird dir hier ohne etwas Code wohl niemand weiterhelfen können. Generell könnte es eine gute Idee sein, bei größeren Abweichungen vom "normalen" Verhalten der Tabelle eine konzeptionell sauberere Implementierung zugrunde zu legen (habe ich zwar noch nicht ausprobiert, aber um ein Beispiel zu nennen: JXTable von SwingLabs)


----------



## OlliL (30. Nov 2012)

Ja, das ist auch eine eigene Klasse welche 2 Eigenschaften (id, description) hat, aber die ComboBoxItem Klasse soll das Problem nicht sein. Mit meinem letzten Edit habe ich ja gesagt, das es auch mit dem DefaultCellEditor das Problem gibt.

Code posten... nicht so trivial da es hier inzw. nicht mehr nur "etwas Code" ist 
Hier mal mit Default-Editor:

Meine View:

```
public class NewMoneyFlowView extends EmptyPanel implements PanelInterface {
	protected NewMoneyFlowTableModel tableModel;
	private JTable table = new JTable();
...
	public JPanel getPanel() {
...
		JPanel panel = new JPanel();
		Vector<ComboBoxItem> model;
		JComboBox<ComboBoxItem> comboBox;
...
		List<ContractPartner> contractPartnerList = contractPartnerDao
				.getAllContractPartner(user);
		model = new Vector<ComboBoxItem>();
		model.addElement(new ComboBoxItem(0, ""));

		for (ContractPartner i : contractPartnerList) {
			model.addElement(new ComboBoxItem(i.getContractPartnerId(), i
					.getName()));
		}
		comboBox = new JComboBox<ComboBoxItem>(model);
		table.getColumnModel()
			.getColumn(NewMoneyFlowTableModel.CONTRACTPARTNER_INDEX)
			.setCellEditor(new DefaultCellEditor(comboBox));
...
		return panel;
	}
...
	public void stopEdit() {
		if (table.getCellEditor() instanceof TableCellEditor)
			table.getCellEditor().stopCellEditing();
	}

	public void errorCell(int row, int col) {
		int realCol = col;

		for (realCol = 0; realCol < table.getColumnCount(); realCol++) {
			if (table.getColumnModel().getColumn(realCol).getModelIndex() == col) {
				break;
			}
		}
		// table.changeSelection(row, realCol, false, false);
		table.editCellAt(row, realCol);
		table.getCellEditor().stopCellEditing();
		return;
	}
```

Mein Controller:

```
public class NewMoneyFlowController implements ActionListener {
	private NewMoneyFlowModel model;
	private NewMoneyFlowView view;
	private MenuHandler event;
	private User user;
	private MoneyFlowValidator validator;

	public void actionPerformed(ActionEvent e) {

		int numRows = model.getRecordSize();

		validator = new MoneyFlowValidator(user);
		view.stopEdit();

		if (!view.errorRaised()) {
			for (int row = 0; row < numRows; row++) {

				if (!model.rowIsEmpty(row)) {
					System.out.println("not empty row " + row);
					if (validator.validateContractPartner(model
							.getContractPartner(row)) > 0) {
						System.out.println("contractpartner");
						view.errorCell(row,
								NewMoneyFlowTableModel.CONTRACTPARTNER_INDEX);
						return;
...
```

Der Controller löst nun in der 2. Spalte ein errorCell() in der View aus, der in der ComboBox kein "contractpartner" selektiert wurde. In der View wird dann table.editCellAt(row, realCol) ausgeführt. Zu diesem Zeitpunkt liefert getValueAt des Table-Models ein ComboBoxItem zurueck welches aus 0, "" besteht. nach table.getCellEditor().stopCellEditing() in errorCell() ist aber in der ComboBox das gleiche Item wie in Zeile 1 der Tabelle ausgewählt.

Edit ich muss den Fehler noch konkretisieren: Es wird nicht immer der Wert der 1. Zeile eingeblendet, sondern der Wert welchen ich in egal welcher Zeile bewusst zuletzt selektiert habe. Also eigentlich der Wert des "letzten" Editors.


----------



## Michael... (30. Nov 2012)

Wenn ich's richtig verstanden habe verwendest Du nicht diese simple ComboBoxEditor Variante:
How to Use Tables (The Java™ Tutorials > Creating a GUI With JFC/Swing > Using Swing Components)
Sondern hast - zwecks Validierung - Deinen eigenen Editor definiert.

Warum und von wo aus rufst Du das hier auf: 
	
	
	
	





```
table.getCellEditor().stopCellEditing()
```
?
Das sieht mir eher nach einem üblen Hack aus. Der Editor sollte sich selbst "beenden".
Meine Vermutung ist, dass der Fehler in Deinem Editor zu finden ist. Daher wäre es mal interessant wie der den ausschaut.


----------



## OlliL (30. Nov 2012)

Hallo Michael,

Ich glaube unsere 2 Postings haben sich ueberschnitten (siehe #3).

Wie ich ja schrieb - der Fehler tritt *auch* mit dem DefaultCellEditor auf.


```
table.getColumnModel()
            .getColumn(NewMoneyFlowTableModel.CONTRACTPARTNER_INDEX)
            .setCellEditor(new DefaultCellEditor(comboBox));
```

Ich rufe stopCellEditing von meinem Save-Button aus auf. zuerst via "view.stopEdit()" damit das Feld in welchem sich der Benutzer beim Click des Buttons befindet validiert wird und im Datenmodel abgelegt wird. Und dann nochmal in errorCell() um 1. in den Editier-Modus zu wechseln und 2. diesen direkt wieder zu Verlassen um die Validierung im Editor zu durchlaufen und die entsprechende Fehlermeldung anzuzeigen.


----------



## Michael... (30. Nov 2012)

Heißt die Validierung wird nicht im Editor selbst vorgenommen? Sondern beim/nach dem Speichern im TableModel? ... um dann gegeben falls wieder den Editor "anzuwerfen"?


OlliL hat gesagt.:


> ```
> table.editCellAt(row, realCol);
> table.getCellEditor().stopCellEditing();
> ```


Sowas schaut nach einer Verzweiflungstat aus. Das kann nicht die richtige Vorgehensweise sein.


OlliL hat gesagt.:


> Edit ich muss den Fehler noch konkretisieren: Es wird nicht immer der Wert der 1. Zeile eingeblendet, sondern der Wert welchen ich in egal welcher Zeile bewusst zuletzt selektiert habe. Also eigentlich der Wert des "letzten" Editors.


 => Entweder Du überschreibst fälschlicher Weise irgendwelche Werte im Model oder Du hast ein Problem im Editor.

Allgemein werde ich aus dem Code und den Beschreibungen nicht wirklich schlau. Für mich schaut es so aus als hättest Du Dich irgendwie in eine Sackgasse manövriert. Würde eigentlich sagen poste mal ein kurzes, kompilierbares Beispiel an dem man das Problem nachvollziehen kann, befürchte aber, dass das nicht so einfach sein wird.

Wie und wo passiert die Validierung? Was soll passieren, wenn die Validierung negativ ausfällt?


----------



## OlliL (30. Nov 2012)

Die Validierung findet im CellEditor statt.

Ich glaube mit einem leicht reduzierten Code-Beispiel wird es einfacher zu verstehen. Um einen Fehler im ComboBox-CellEditor auszuschliessen verwendet das Beispiel den DefaultCellEditor für die Comboboxen.

Test.zip

Das Fenster dann in folgenden Zustand versetzen:

Directupload.net - jdxw7btt.png

Und dann unten auf "Speichern" klicken. Dann wird in Zeile 2 der Vertragspartner auch auf "Karl Heinz" gesetzt.


----------



## OlliL (30. Nov 2012)

Falls es noch zu kompliziert sein sollte:

Ich habe das ganze noch simpler gestrickt und mehr Zeugs über Board geworfen um den Fehler zu zeigen:

Test.zip
(unten auf "Click here to start download from sendspace" klicken)

Folgende Ansicht herstellen, und dann auf "Speichern" klicken.

Directupload.net - ue7xmdl6.png

actionPerformed() würde nun im echten Leben feststellen, das die ComboBox in der 2. Zeile nicht ausgewählt wurde, dahin springen, in den Edit-Modus wechseln und diesen dann verlassen um die Validierung im CellEditor "anzusprechen". Das habe ich alles weg gelassen. Es gibt keinen eigenen Cell-Editor und damit auch keine Validierung. Einfach das hinspringen und stopCellEditing reicht schon aus, den Wert aus Zeile 1 zu "kopieren". 

Kommentiert man stopCellEditing in View.errorCell aus, sieht man, das schon mit editCellAt der Wert übernommen wird.

Das Augenmerk liegt hier nicht so sehr auf Zeile 1. Man kann z.B. auch Zeile 3 ausfuellen und dann auf den Button klicken, dann wird der Wert aus Zeile 3 übernommen. Es wird immer der zuletzt gewählte Wert dieser column übernommen.


----------



## Michael... (30. Nov 2012)

Mir ist zwar immer noch nicht klar wofür die Methode errorCell gut ist - um Zellen mit falschem Inhalt "anzuspringen"?

Egal, die Ursache Deines Problems ist Deine ComboBoxItem. Der Grund warum im noch leeren ComboBoxEditor die letzte Selektion angezeigt wird ist (dass eine ComboBox für alle Zellen als Editor dient wurde ja bereits erwähnt), dass auf den Punkt gebracht 
	
	
	
	





```
new ComboBoxItem(0, "").equals(new ComboBoxItem(0, ""))
```
 false liefert.

Erläuterung: Wird eine Zelle editiert, wird dem Editor der Wert in der Zelle übergeben. Einem Textfeld ist der egal es nimmt ihn einfach entgegen und stellt in dar. Eine ComboBox kann aber nur Werte anzeigen, die in auch deren Model enthalten sind. Wird nun in der 
	
	
	
	





```
setSelectedItem(...)
```
 ein Wert übergeben, der nicht im Model enthalten ist, belässt die ComboBox die Selektion beim zuletzt gültigen Wert.
"Der Wert muss im Model enthalten sein" bedeutet, dass das in der Methode übergebene Objekt nicht unbedingt identisch, aber zumindest gleich (im Sinne von equals) sein muss.

Die Lösung: Überschreibe in Deiner Klasse ComboBoxItem die Methoden 
	
	
	
	





```
equals()
```
 und 
	
	
	
	





```
hashCode()
```
 - wobei das Überschreiben ersterer zur Problembehebung schon reichen sollte.

Alternativ kannst Du auch das identische ComboBoxItem in die ComboBox und die leeren Zellen des TableModel stecken.


----------



## OlliL (30. Nov 2012)

bzgl errorCell:

Ich habe im CellEditor Feldprüfungen für alle Felder der Tabelle. Damit will ich verhindern, dass der User Pflichtfelder leer läßt, oder falsch befüllt. 
Nun kann es aber sein, das der User direkt ohne irgendwelche Eingaben (leere Tabelle) auf den Speichern-Button klickt. Nun sind evtl. Pflichtfelder leer.
Um das abzufangen laufen ich nun im Controller durch das Modell und suche Felder welche der Anwender noch nicht eingegeben hat.
Finde ich Felder möchte ich den User dazu zwingen diese Felder einzugeben. Um nun die Fehlermeldung an den User (betroffenes Feld rot umranden, Fehlermeldung in nem Dialog anzeigen) nicht nochmal zu programmieren, habe ich mir gedacht waere es das simpelste einfach den User in diese Zelle zu schicken und den Editiervorgang zu beenden - nun wird  die Fehlerprüfung aus meinem CellEditor durchlaufen und die macht schon all das was ich gemacht haben will.

Keine gute Idee?


Bzgl deinem anderen Hinweis:
Suppi! Dann schaue ich jetzt mal was ich mache.

Ich habe gerade auch gemerkt, das wenn ich das Combobox-Feld in meinem Modell mit "null" initialisiere anstatt mit "new ComboBoxItem..." klappt es auch. Dann führt er jedoch CellEditor.stopCellEditing 2 mal aus. Einmal beim anklicken der ComboBox, und ein weiteres mal beim auswählen des Wertes. Beim ersten Mal ist table.isEditing() false, beim zweiten Mal true. Muss man das verstehen? 

Ich habe gerade auf "null" Initialisierung der ComboBoxen im Model umgestellt und in stopCellEditing den Durchlauf der Fehlerprüfungen nur bei isEditing()==true (ansonsten kommen die Fehlermeldungen nun 2 mal).

Das "scheint" zu tun....


----------



## Michael... (30. Nov 2012)

Ich würde das Speichern nur ermöglichen (den Speichern Button aktivieren), wenn das Speichern auch möglich ist.
Der Aufwand für einen eigenen CellRenderer das "managen" leerer Pflichtfelder sollte überschaubar sein.


----------



## OlliL (30. Nov 2012)

Gute Idee mit dem CellRenderer.
Aber - wer sagt bei nicht klickbarem "Speichern" Button dem User warum er nicht klickbar ist und wie er das "beheben" kann?


----------



## Michael... (3. Dez 2012)

Man könnte sich an dem Verhalten orientieren, was die meisten Nutzer vom Ausfüllen von Formularen kennen dürften. Ein Infotext, z.B. neben dem Button a la "Bitte Pflichtfelder ausfüllen" und die Felder "highlighten"


----------

