Hallo erstmal!
Ich schreibe gerade an einer Applikation, die mit Hilfe einer Datennahmekomponente Daten aus der "realen Welt" (Spannungen, Ströme, Temperaturen und Warnungen) sammelt und diese dann (unter anderem) in einer GUI darstellen soll.
Ich will dafür (wiederum unter anderem) eine JTable nutzen, die genau 4 Spalten hat:
"Name" (des Wertes), "Type" (also z.B. FLOAT, INTEGER, STRING), "Value" (z.B. -19.5) und "Timestamp" (Zeitpunkt der Datennahme).
Sobald die Datennahmekomponente jetzt einen neuen Wert holt, einen bestehenden Wert aktualisiert, bzw. einen bestehenden Wert verwirft (löscht), reagiert die Tabelle (genauer gesagt mein TableModel) darauf, indem eine Zeile hinzugefügt, aktualisert oder entfernt wird.
Dadurch soll sichergestellt werden, dass meine Tabelle zu jedem Zeitpunkt den aktuellen Datensatz widerspiegelt.
Tatsächlich klappt das auf den ersten Blick hervorragend. Mein Applikationsfenster verhält sich exakt so, wie ich es erwarten würde. Insbesondere werden also die Tabellenzeilen ordnungsgemäß verwaltet und angezeigt.
Leider gibt es dennoch einige unschöne Nebeneffekte, denn das Konsolenfenster in Eclipse gibt folgende Ausgabe:
Nochmal zur Erinnerung: die Tabelle verhält sich dabei wie gewünscht.
Ich habe bereits einen etwas erfahreneren Kollegen gefragt, woran das liegen könnte.
Er sieht ein Grundproblem darin, dass meine Datannahmekomponente, deren Aktionen ja im Endeffekt das Neuzeichnen der Tabelle triggern, völlig unsynchronisiert mit meiner Tabelle sind.
Insbesondere kommt es häufig vor, dass etwa 20 Zeilen innerhalb kürzerster Zeit geändert werden müssen. Da die Datennahmekomponente allerdings jeweils ein Datum ändert, führt dies zu 20 aufeinanderfolgenden Neuzeichnungen der Tabelle.
Natürlich wäre es hier schön, das Neuzeichnen erst nach Abschluss der Datenaktualisierung durchzuführen.
Allerdings setzt dies voraus, dass ich weiß, wann ein "Stoß" Daten angekommen ist. Und das wiederspräche dem datengetriebenen Charakter meiner Applikation (die Aussenwelt bestimmt wann, welche Daten kommen / gehen).
Hier sind die relevanten Teile meines Codes:
Das Panel:
Sowie mein TableModel:
Vielen Dank für die Hilfe schonmal im Voraus!
Ich schreibe gerade an einer Applikation, die mit Hilfe einer Datennahmekomponente Daten aus der "realen Welt" (Spannungen, Ströme, Temperaturen und Warnungen) sammelt und diese dann (unter anderem) in einer GUI darstellen soll.
Ich will dafür (wiederum unter anderem) eine JTable nutzen, die genau 4 Spalten hat:
"Name" (des Wertes), "Type" (also z.B. FLOAT, INTEGER, STRING), "Value" (z.B. -19.5) und "Timestamp" (Zeitpunkt der Datennahme).
Sobald die Datennahmekomponente jetzt einen neuen Wert holt, einen bestehenden Wert aktualisiert, bzw. einen bestehenden Wert verwirft (löscht), reagiert die Tabelle (genauer gesagt mein TableModel) darauf, indem eine Zeile hinzugefügt, aktualisert oder entfernt wird.
Dadurch soll sichergestellt werden, dass meine Tabelle zu jedem Zeitpunkt den aktuellen Datensatz widerspiegelt.
Tatsächlich klappt das auf den ersten Blick hervorragend. Mein Applikationsfenster verhält sich exakt so, wie ich es erwarten würde. Insbesondere werden also die Tabellenzeilen ordnungsgemäß verwaltet und angezeigt.
Leider gibt es dennoch einige unschöne Nebeneffekte, denn das Konsolenfenster in Eclipse gibt folgende Ausgabe:
Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: 2 >= 2
at java.util.Vector.elementAt(Unknown Source)
at javax.swing.table.DefaultTableColumnModel.getColumn(Unknown Source)
at javax.swing.plaf.basic.BasicTableUI.paintCells(Unknown Source)
at javax.swing.plaf.basic.BasicTableUI.paint(Unknown Source)
at javax.swing.plaf.ComponentUI.update(Unknown Source)
at javax.swing.JComponent.paintComponent(Unknown Source)
at javax.swing.JComponent.paint(Unknown Source)
at javax.swing.JComponent.paintWithOffscreenBuffer(Unknown Source)
at javax.swing.JComponent.paintDoubleBuffered(Unknown Source)
at javax.swing.JComponent._paintImmediately(Unknown Source)
at javax.swing.JComponent.paintImmediately(Unknown Source)
at javax.swing.RepaintManager.paintDirtyRegions(Unknown Source)
at javax.swing.SystemEventQueueUtilities$ComponentWorkRequest.run(Unknown Source)
at java.awt.event.InvocationEvent.dispatch(Unknown Source)
at java.awt.EventQueue.dispatchEvent(Unknown Source)
at java.awt.EventDispatchThread.pumpOneEventForHierarchy(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.run(Unknown Source)
Nochmal zur Erinnerung: die Tabelle verhält sich dabei wie gewünscht.
Ich habe bereits einen etwas erfahreneren Kollegen gefragt, woran das liegen könnte.
Er sieht ein Grundproblem darin, dass meine Datannahmekomponente, deren Aktionen ja im Endeffekt das Neuzeichnen der Tabelle triggern, völlig unsynchronisiert mit meiner Tabelle sind.
Insbesondere kommt es häufig vor, dass etwa 20 Zeilen innerhalb kürzerster Zeit geändert werden müssen. Da die Datennahmekomponente allerdings jeweils ein Datum ändert, führt dies zu 20 aufeinanderfolgenden Neuzeichnungen der Tabelle.
Natürlich wäre es hier schön, das Neuzeichnen erst nach Abschluss der Datenaktualisierung durchzuführen.
Allerdings setzt dies voraus, dass ich weiß, wann ein "Stoß" Daten angekommen ist. Und das wiederspräche dem datengetriebenen Charakter meiner Applikation (die Aussenwelt bestimmt wann, welche Daten kommen / gehen).
Hier sind die relevanten Teile meines Codes:
Das Panel:
Code:
public class GUI_factWindow extends JPanel implements WorkingMemoryEventListener {
private static final long serialVersionUID = 1L;
public GUI_tableModel model;
public JTable table;
public JScrollPane scrollPane;
public GUI_factWindow() {
model = new GUI_tableModel();
table = new JTable(model);
scrollPane = new JScrollPane(table);
scrollPane.setPreferredSize(new Dimension(900,200));
this.setBorder(BorderFactory.createTitledBorder("Facts"));
this.add(scrollPane, BorderLayout.CENTER);
}
public void objectInserted(ObjectInsertedEvent arg0) {
model.addRow(arg0.getObject());
}
public synchronized void objectRetracted(ObjectRetractedEvent arg0) {
model.removeRow(arg0.getOldObject()) ;
}
public void objectUpdated(ObjectUpdatedEvent arg0) {
model.updateRow(arg0.getObject());
}
}
Sowie mein TableModel:
Code:
public class GUI_tableModel extends AbstractTableModel{
private static final long serialVersionUID = 1L;
private String[] columnNames = {"Name", "Type", "Value", "Timestamp"};
private Hashtable<String, Object> data;
private Hashtable<Integer, String> indizes;
private Hashtable<String, Integer> names;
GUI_tableModel() {
super();
this.data = new Hashtable<String, Object>();
this.indizes = new Hashtable<Integer, String>();
this.names = new Hashtable<String, Integer>();
}
public String getColumnName(int column) {
return columnNames[column];
}
public int getColumnCount() {
return 4;
}
public synchronized int getRowCount() {
int rowCount;
try {
rowCount = data.size();
}
catch(NullPointerException e) {
System.out.println(e.getMessage() + e.getCause());
return 1;
}
return rowCount;
}
public synchronized Object getValueAt(int row, int column) {
//Are the indizes allowed?:
if((column >= 4) || (column < 0) || (row >= this.getRowCount()) || (row < 0)) {
return null;
}
String[] rowAsStringField;
//Get the object as a string field:
try {
rowAsStringField = getRowFromString(data.get(indizes.get(row)).toString());
}
catch (NullPointerException e) {
System.out.println(e.getMessage());
return null;
}
//Do the switch for the seperate columns:
switch(column) {
case 0: //The "name" column
return rowAsStringField[0];
case 1: //The "type" column
return rowAsStringField[1];
case 2: //The "value" column
return rowAsStringField[2];
case 3: //The "timestamp" column
return rowAsStringField[3];
}//end switch
//Unreacheable, if everything works, but makes the compiler happy... ;-)
return null;
}
public synchronized void updateRow(Object arg0) {
String[] row = getRowFromString(arg0.toString());
int rowNumber = this.findColumn(row[0]);
this.setValueAt(row[1], rowNumber, 1);
this.setValueAt(row[2], rowNumber, 2);
this.setValueAt(row[3], rowNumber, 3);
this.fireTableRowsUpdated(rowNumber, rowNumber);
}
public synchronized void addRow(Object arg0) {
String[] row = getRowFromString(arg0.toString());
data.put(row[0], arg0);
names.put(row[0], indizes.size());
indizes.put(indizes.size(), row[0]);
this.fireTableRowsInserted(indizes.size(), indizes.size());
}
public synchronized void removeRow(Object arg0) {
String[] row = getRowFromString(arg0.toString());
int rowNumber = names.get(row[0]);
data.remove(row[0]);
indizes.remove(rowNumber);
names.remove(row[0]);
this.fireTableRowsDeleted(rowNumber, rowNumber);
}
private String[] getRowFromString(String stringRepresentation) {
String[] row = stringRepresentation.split(" ");
//The time format contained an " " and was therefore split. Merge it by hand:
if(row.length >= 4) {
row[3] = row[3] + " " + row[4];
}
return row;
}
}
Vielen Dank für die Hilfe schonmal im Voraus!