Tatsächlich hatte ich auch schon öfter (aber nicht so “ernsthaft”) mal in Betracht gezogen, so eine Art “Mini-Tabellenkalkulation” auf Basis einer JTable zu erstellen. Könnte ganz unterhaltsam sein.
Ich denke, SlaterB hat die wesentlichen Optionen zusammengefasst, auch wenn die Abgrenzung zwischen den Fällen nicht 100% klar ist.
Nochmal etwas anders (Vielleicht ist es auch nur anders formuliert?) :
-
getValueAt() berechnet “seinen” Inhalt vollkommen autark. Die Zelle ist dabei rein funktional und rein passiv. D.h. die Daten für diese Zelle werden nicht gespeichert, sondern immer on-the-fly ausgerechnet. (Bei jeder Änderung wird brutal und pauschal mit fireTableDataChanged() dafür gesorgt, dass alle Listener von den neuen Werten was mitbekommen - im speziellen also dass die JTable mit den neuen, eventuell geänderten Werten frisch gezeichnet wird).
-
Bei einem setValueAt() wird über die ganze Tabelle gegangen, und mit einer “internen” Funktion der neue Wert berechnet, der dann gesetzt wird:
for (each cell r,c) {
Object value = computeNewValueAt(r,c);
setValueAt(value, r,c);
}
Der entscheidende Unterschied wäre, dass die Daten (also die Endergebnisse) wirklich in den Zellen gespeichert sind.
Die dritte Variante…
3. von einer konkreten Änderung aus wissen, was passiert, nach und nach Werte ersetzen,
fällt IMHO erstmal weg: Eine Tabellenzelle sollte nicht direkt wissen, welche anderen Zellen von “ihrem” Wert abhängen
Und die Frage, ob sie das praktikabel überhaupt wissen, kann, ist auch interessant - das führt zum nächsten:
IMHO fällt aus dem gleichen Grund auch die 2. Variante weg: Wenn die Zellen die Werte wirklich speichern, weiß man praktisch nie, ob eine Zelle einen aktuellen Wert enthält oder nicht.
Das “wegfallen” bezieht sich auf den Fall, dass man eine einfache Lösung will.
Die einzige Option, die ich sehen würde, um die 2. oder die 3. Option umzusetzen, wäre ein AST. In der Zelle müßte ein Abstract Syntax Tree gespeichert sein, an dem eindeutig und programmatisch (!) erkannt werden kann, welche Zelle von welcher anderen abhängt. Das dürfte aber etwas aufwändiger sein…
Ich hätte eigentlich vermutet, dass die erste Variante die einfachste sein könnte - GROB anskizziert:
package bytewelt;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
public class FunctionCalculatorTableModel extends DefaultTableModel {
interface FunctionalCell
{
Double getValue(int row, int column);
}
static FunctionalCell createComputingFunctionalCell(
final TableModel tableModel)
{
return new FunctionalCell()
{
@Override
public Double getValue(int row, int column)
{
double A = asDouble(tableModel.getValueAt(row, 1));
double B = asDouble(tableModel.getValueAt(row, 2));
double C = asDouble(tableModel.getValueAt(row, 4));
return A-B+C;
}
};
}
static FunctionalCell createConditionalFunctionalCell(
final TableModel tableModel)
{
return new FunctionalCell()
{
@Override
public Double getValue(int row, int column)
{
if (row == 0)
{
return null;
}
double otherValue = asDouble(tableModel.getValueAt(row-1, 3));
if (otherValue < 0)
{
return otherValue;
}
return null;
}
};
}
private static double asDouble(Object object)
{
if (object == null)
{
return 0.0;
}
if (object instanceof Number)
{
Number number = (Number)object;
return number.doubleValue();
}
return 0.0;
}
public FunctionCalculatorTableModel() {
super(10, 5);
for (int r=0; r<10; r++)
{
setValueAt(createComputingFunctionalCell(this), r, 3);
}
for (int r=0; r<10; r++)
{
setValueAt(createConditionalFunctionalCell(this), r, 1);
}
}
@Override
public String getColumnName(int column) {
if(column == 0) return "Nummer";
if(column == 1) return "A";
if(column == 2) return "B";
if(column == 3) return "A-B+C";
if(column == 4) return "C";
return null;
}
@Override
public Class<?> getColumnClass(int columnIndex) {
if(columnIndex == 0) return Integer.class;
return Double.class;
}
@Override
public void setValueAt(Object aValue, int row, int column)
{
super.setValueAt(aValue, row, column);
fireTableDataChanged();
}
@Override
public Object getValueAt(int row, int column) {
System.out.println("getValueAt(Zeile "+row+", Spalte "+getColumnName(column)+") betreten");
if(column == 0) return row+1;
Object value = super.getValueAt(row, column);
if (value instanceof FunctionalCell)
{
FunctionalCell functionalCell = (FunctionalCell)value;
Double functionalValue = functionalCell.getValue(row, column);
return functionalValue;
}
return value;
}
}
Aaaber ich weiß nicht, ob man um eine programmatische, an einen Abstract Syntax Tree angelehnte Repräsentation der Zelleninhalte/Formeln drumrumkommt. Spätestens bei Zirkelbezügen würde man sonst immer auf die Fresse fliegen:
Column A = B+C
Column B = A+C
Column C = B+A
Hab’s gerade mal getestet: Excel erkennt das (natürlich), und blendet dann einen Warndialog ein…