# DefaultTableModel setDataVector



## muckelzwerg (7. Mrz 2011)

Edit: Die Sache hat sich geklärt. Wer sich dafür interessiert, darf natürlich gerne trotzdem was dazu sagen. Aber Pfadfinderenergie braucht ihr hier nicht mehr aufwenden. 
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Hiho, folgende Situation:
Ich habe eine JTable, deren Daten bei jedem Neuzeichnen aktualisiert werden. (Die Tabelle zeigt Sensormessungen an)
Falls die Daten der entsprechenden Objekte sich geändert haben (muss nicht bei jedem Frame sein, KANN aber), wird das TableModel mit

```
model.setRowCount(0);
"loop"
    model.addRow(data[...])
```
neu befüllt. 
Das Überprüfen und falls notwendig Aktualisieren des Datenmodels wird über die paintComponent des übergeordneten Panels (oberhalb der ScrollPane der Tabelle) angestoßen.
Etwa alle 10ms wird in der Anwendung ein repaint() gepostet. (Die Rate trifft natürlich nicht 100%, das ist aber nicht weiter schlimm) Die Sensordaten werden unabhängig, aus einem anderen Thread in die jeweiligen Objekte übertragen. (teilweise mit etwa 200Hz) Sobald die Tabelle neu gezeichnet wird, wird geprüft, ob sich an den Sensordaten etwas verändet hat. Falls das der Fall ist, wird das TableModel neu geschrieben.

Soweit funktioniert das auch ganz gut, ohne Darstellungsprobleme. Jetzt sagt mir aber mein Gefühl, dass es doch eigentlich schneller gehen müsste, wenn man
model.setDataVector(...) verwendet und die kompletten Daten auf einmal überträgt.
Allerdings setzt diese Methode wirklich nur den neuen Datenvektor. Anderes als setRowCount() und addRow(), feuert sie nicht die entsprechenden Events, dass sich die Daten geändert haben.
Dementsprechend ist die JTable dann "verwirrt" und muss ihre Darstellung neu ermitteln. Das sorgt anscheinend für böses Flimmern, weil die JTable erst ohne die passende Größe und dann korrekt resized angezeigt wird. 
Das sieht dann so aus, als ob bei jedem zweiten Zeichnen die Tabelle mit der minimalen Feldbreite gezeichnet wird. Sie nutzt das ScrollPane nur zu einem Drittel aus. Im Wechsel dazu wird die Tabelle dann mit der angepassten Breite gezeichnet und füllt das ScrollPane komplett. Die Zeilen sind IMMER alle vorhanden und IMMER mit Daten befüllt. (weiteres zu "threadsafe" weiter unten)

Die Funktion setRowCount() feuert 

```
// durch setRowCount Zeilen entfernt
fireTableRowsDeleted(rowCount, oldCount-1);

// durch setRowCount Zeilen hinzugefügt
fireTableRowsInserted(oldCount, rowCount-1);
```
und bei addRow() wird wieder "fireTableRowsInserted(rowCount, rowCount)" abgesetzt
Das AbstractTableModel sagt zu den Funktionen

```
fireTableRowsDeleted(int firstRow, int lastRow)
Notifies all listeners that rows in the range [firstRow, lastRow], inclusive, have been deleted.
Parameters:
    firstRow the first row
    lastRow the last row


fireTableRowsInserted(int firstRow, int lastRow)
Notifies all listeners that rows in the range [firstRow, lastRow], inclusive, have been inserted.
Parameters:
    firstRow the first row
    lastRow the last row
```

Jetzt stellt sich die Frage, wie ich die Events absetzen muss, um den Datenvektor direkt schreiben zu können, ohne jede Zeile einzeln hinzuzufügen.
Wenn ich setRowCount(0) zum Löschen der Tabelle erstmal weiterverwende müsste anschließend doch ein

```
model.setDataVector( "dataVector",  "columnVector");
und
model.fireTableRowsInserted(0, "dataVector.size()");
```
möglich sein?
Aber auch hier flimmert die Tabelle wieder.
Also stimmt da wohl etwas mit den Events noch nicht?


Weiterer Gedanke: Das DefaultTableModel ist nicht threadsafe. Man bekommt also Probleme, wenn man aus einem Thread Daten ändert und Swing in dem Moment gerade zeichnen will.
Das sollte aber eigentlich nicht das Problem sein. Die nicht-skalierte "schmale" Darstellung der Tabelle, enthält sämtliche Zeilen und alle Daten.
Es ist also nicht so, dass schon gezeichnet wird bevor die Tabelle wieder konsistent ist.
Außerdem wird die paintComponent ja vom Swing Renderthread ausgeführt. Egal ob die JTable nun vor oder nach dem Aktualisieren der Werte gezeichnet wird, kann der Thread das ja nicht mischen.
Desweiteren würde das Flimmern dann auch nicht verschwinden, wenn jede Zeile einzeln hinzugefügt wird. 
Würde der Renderthread zwischen "setDataModel" und "fireEvent" unterbrechen, dann würde er das genauso auch während "addRow" tun, bevor dort intern "fireEvent" ausgeführt wird. (passiert aber nicht)
Zudem hab ich noch ein paar Tests gemacht. Ich finde keinen weg, die falsch skalierte, schmale Darstellung von Hand zu erzeugen.
Egal, was ich in die Tabelle schreibe, wird sie immer auf die volle Breite des ScrollPanes gestreckt.
Desweiteren werden auch immer alle Zeilen gesetzt. Es gibt keine Unterbechungen und kaskadierende Befüllung.


Joa, ziemlich viel Text für eine Sache, die bereits zufriedenstellend funktioniert. 
Bin mal gespannt, ob überhaupt irgendwer was dazu sagen kann.


----------



## Michael... (7. Mrz 2011)

Hab den Text nur überflogen, aber das ganze mit setRowCount(0) und alle 10ms ein repaint()... hört sich ein bisschen merkwürdig an.

Wenn Du die kompletten Daten auf einmal austauschen willst, würde ich ein eigenes TableModel schreiben, welches von DefaultTableModel erbt.
Dieses Model musst Du dann nur um eine Methode erweitern, in der Du die Referenz von dataVector auf den neuen Vector setzt und anschließend fireDataChanged() aufrufen. Die JTable zeichnet sich dann automatisch neu.


----------



## muckelzwerg (7. Mrz 2011)

Kaum macht man es richtig ...
Das DefaultTableModel hat ja bereits eben genau diese Methode "setDataVector()", da brauch ich die nicht nochmal selbst schreiben. 
Der Punkt ist aber nicht das Fehlen der passenden Events, sondern dass dort "unpassende" Events geworfen werden.
Wenn der Vektor über diese Methode geändert wird, dann wird "fireTableStructureChanged()" angestoßen.
Und das führt zum kompletten revalidieren der Darstellung.
Schreibe ich die Tabelle wie gewohnt zeilenweise und füge nur dieses eine (in dem Fall unnötige) event ein, bekomme ich auch dort das Flimmern/Zucken.

Das Model muss davon ausgehen, dass bei einem komplett neuen Vektor die komplette Struktur geändert wurde und muss dementsprechend die Darstellung komplett neu ermitteln. Das erzeugt das Zucken. Setzte ich die Framerate runter auf z.B. 1Hz,
bleibt es beim Zucken. Es kommt keine verzerrte Darstellung von 1sek Anzeigedauer zustande. 

Die Lösung:
Da ich es "besser" weiß, als das TableModel und sicher sein kann, dass sich die Tabellenstruktur (Spalten) nicht geändert haben, hole ich den aktuellen Vector aus dem Model, [st]lösche[/st] ähm leere (Java call by value of reference ...) ihn und füge die neuen Daten direkt in den Vektor ein. 
Das umgeht "setDataVector" und das Aktualisieren der Tabelleenstruktur und spart gleichzeitig die Events und den weiteren Overhead, der bei den vielen einzelnen "addRow" Aufrufen entsteht.



Unabhängig davon, was stört Dich an "setRowCount(0)" und dem repaint?
Mir war "setRowCount" zum Löschen auch etwas komisch, ist aber eben der Weg. Es gibt keine "Clear" Methode und wenn man den Vector direkt leert, fehlen wieder die entsprechenden Events.



Edit: Ist "Strikethrough" bei den Codes nicht aktiviert?


----------



## Michael... (7. Mrz 2011)

muckelzwerg hat gesagt.:


> Das DefaultTableModel hat ja bereits eben genau diese Methode "setDataVector()", da brauch ich die nicht nochmal selbst schreiben.


Korrekt, aber wie Du selbst festgestellt hast wird beim Verwenden dieser Methode *fireTableStructureChanged()* aufgerufen. Was in Deinem Fall (abgesehen davon, dass das Layout, Spaltenbreite usw. "zerschossen" wird) unnützt ist und bei der hohen Updaterate unnötig Last erzeugt.
Daher mein Vorschlag schreib Dir eine eigene Methode die nur die Referenz des Vectors umbiegt und nur *fireDataChanged()*  aufruft. Damit bleibt die Darstellung der Tabelle erhalten und nur die Anzeige der Daten wird aktualisiert.


----------



## muckelzwerg (7. Mrz 2011)

Momentan verwende ich "getDataVector()", "v.removeAllElements()" und "Collections.copy(v, vnew)", fertig. Drei Zeilen.
Das ist mir dann doch etwas lieber, als ein abgeleitetes DataModel, mit der neuen Methode "setDataVectorWithSameStructure(vNew)", die auch nur wieder die Zeile "Collections.copy(v, vnew)" enthält. 
Und "fireDataChanged" ist so oder so nicht nötig, weil das repaint ja regelmäßig ausgeführt wird.


----------



## Michael... (7. Mrz 2011)

Warum das Kopieren? Und wie gesagt den Aufruf von repaint() per Timer o.ä. kannst Du Dir sparen.
Mit den zwei von mir vorgeschlagenen Codezeilen, wird in der Tabelle neu gezeichnet, wenn es etwas zu zeichnen gibt.


----------



## muckelzwerg (7. Mrz 2011)

Du hast vorhin geschrieben "habs nur überflogen" vermutlich liegts daran. 

Die Sensordaten werden (natürlich) in einem anderen Thread ermittelt. Dabei kommen Datenraten von 200Hz und auch deutlich mehr zustande. Zwei Pixart IR-Cams (wiimote) kommen über I2C schon auf sehr viel mehr Samples. Und da kommt noch einiges mehr dazu.

Wenn ich nun das komplette "OnDemandRendering" von java verwende, dann ergibt sich

1) Ich setzte den Datenvektor einmal auf den Vektor des Sensors und lasse ihn dann immer vom Sensor verändern.
-> Problem ist dabei dass die Funktionen des DataModels nicht threadsafe sind und dass eine Synchronisation Leistung kosten würde.
-> Problem 2 ist, dass ich dann viele hundert (oder gar tausend) Aktualisierungen pro Sekunde habe, die letztlich dann doch nicht zu einem Neuzeichnen führen. Ich kann zwar 1000mal in der Sekunde "dataChanged" aufrufen, aber bekomme ja keine entsprechende Bildrate.

2) Ich setze den Datenvektor nicht bloß ein einziges mal neu, sondern jedesmal. Dabei setze ich ihn auf einen Vektor der vom Sensor zu genau diesem Zweck erzeugt und nie wieder angefasst wird. 
-> Das Problem der Synchronisation wäre damit gelöst, da der Vektor "abgegeben" wird.
-> Problem bleibt wieder die Datenrate. Dann habe ich tausende von neuen Vektoren in der Sekunde, die alle direkt nach einem einzigen mal Verwenden (für die Darstellung der Tabelle) wieder abgeräumt werden. Zudem bleiben auch die vielen "dataChanged" Aufrufe.


Löse ich mich vom "onDemandRendering", bekomme ich eine einigermaßen anständige Bildrate, mit der ich auch beim "Ablesen" der Werte besser arbeiten kann.
Jedesmal wenn eine Komponente gezeichnet wird, überprüft sie, ob die Daten noch aktuell sind. Wieviele hundert mal sich die Daten seit dem letzten Zeichnen geändert haben ist dann uninteressant. Die Änderungen laufen im Sensor immer in den gleichen Vektor und erzeugen keine zusätzliche Arbeit.
Beim Zeichnen muss dieser Vektor dann kopiert werden. Ob ich da nun erst den Vektor kopiere und dann die Referenz in der Tabelle anpasse, oder den einen Vektor übergebe und dann in den Anderen aus der Tabelle kopiere, spielt keine Rolle. 
So komme ich dann auf bspw. 60Hz Bildrate und damit auch 60 Kopien pro angezeigter Tabelle pro Sekunde, im Höchstfall (alle Tabellen haben sich geändert)
und keinen einzigen Aufruf von "dataChanged".
Ist keine Tabelle zu sehen, wird gar nichts kopiert.


----------

