I've been searching for whole day and I still can't find simple anwser to my problem: how can I make JTable cell update it's value when I edit it in another one?
I wanted to use somehow fireTableCellUpdated
but I don't really understand how can I use it, when and on what object.
What I want is to obtain is some kind of listener that listens whether that values changes or not. In this particular scenario I have editable third column in which I store the amount and I want that listener to automatically calculate and set values in other cells in a row. I've come up with something like this:
@Override
public void tableChanged(TableModelEvent e)
{
BigDecimal withoutTax, tax, withTax;
for(int i = 0; i < table.getRowCount(); i++)
{
BigDecimal amount = new BigDecimal(String.valueOf(table.getValueAt(i, 3)).replace(",", "."));
BigDecimal price = new BigDecimal(String.valueOf(table.getValueAt(i, 4)).replace(",", "."));
withoutTax = amount.multiply(price, new MathContext(2));
table.setValueAt(withoutTax, i, 5);
tax = withoutTax.multiply(new BigDecimal(0.23), new MathContext(2));
table.setValueAt(tax, i, 7);
withTax = withoutTax.add(tax, new MathContext(2));
table.setValueAt(withTax, i, 8);
}
}
But this results in StackOverflowError
and I'm guessing that's because table.setValueAt
fires tableChanged
listener so it's going into infinite loop.
Can someone explain me how can I accomplish that?
tableChanged
is been called when a change to the TableModel
is taking place, which is been trigged by the setValueAt
method and around you go...
The solution? Do it inside the setValue
method of the TableModel
...
public class TestModel extends ... { // Some TableModel
//...
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
if (columnIndex == 3) {
// Set the value been passed to in (probably from the editor)...
fireTableCellUpdated(rowIndex, columnIndex);
BigDecimal amount = new BigDecimal(String.valueOf(getValueAt(rowIndex, 3)).replace(",", "."));
BigDecimal price = new BigDecimal(String.valueOf(getValueAt(rowIndex, 4)).replace(",", "."));
BigDecimal withoutTax = amount.multiply(price, new MathContext(2));
// Set the value for row x 5 directly within the backing store of the model...
//table.setValueAt(withoutTax, i, 5);
BigDecimal tax = withoutTax.multiply(new BigDecimal(0.23), new MathContext(2));
// Set the value for row x 7 directly within the backing store of the model...
//table.setValueAt(tax, i, 7);
BigDecimal withTax = withoutTax.add(tax, new MathContext(2));
// Set the value for row x 8 directly within the backing store of the model...
//table.setValueAt(withTax, i, 8);
fireTableCellUpdated(rowIndex, 5);
fireTableCellUpdated(rowIndex, 7);
fireTableCellUpdated(rowIndex, 8);
// It might actually be easier to use...
//fireTableRowsUpdated(rowIndex, rowIndex);
}
}
For example...
This is a basic example, while it only uses a single row, the idea holds true for multiple rows all the same...
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.AbstractTableModel;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new BorderLayout());
JTable table = new JTable(new MultiplicationTableMode());
add(new JScrollPane(table));
}
}
public class MultiplicationTableMode extends AbstractTableModel {
private List<List<Integer>> values;
public MultiplicationTableMode() {
values = new ArrayList<>(1);
List<Integer> cols = new ArrayList<>(11);
for (int index = 0; index < 11; index++) {
cols.add(0);
}
values.add(cols);
}
@Override
public int getRowCount() {
return values.size();
}
@Override
public int getColumnCount() {
return 10;
}
@Override
public String getColumnName(int column) {
return column == 0 ? "?" : "x" + Integer.toString(column);
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return columnIndex == 0;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
List<Integer> columns = values.get(rowIndex);
return columns.get(columnIndex);
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return Integer.class;
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
if (columnIndex == 0) {
if (aValue instanceof Integer) {
List<Integer> columns = values.get(rowIndex);
int intValue = (int) aValue;
columns.set(0, intValue);
for (int index = 1; index < columns.size(); index++) {
columns.set(index, intValue * index);
}
fireTableRowsUpdated(rowIndex, rowIndex);
}
}
}
}
}
Updated...
It occurred to me that if the other column values are not editable, then you don't actually need to maintain their values at all, but can simply calculate them dynamically whenever getValueAt
is called, for example...
public class MultiplicationTableMode extends AbstractTableModel {
private List<Integer> values;
public MultiplicationTableMode() {
values = new ArrayList<>(1);
values.add(0);
}
@Override
public int getRowCount() {
return values.size();
}
@Override
public int getColumnCount() {
return 10;
}
@Override
public String getColumnName(int column) {
return column == 0 ? "?" : "x" + Integer.toString(column);
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return columnIndex == 0;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
int value = values.get(rowIndex);
if (columnIndex > 0) {
value *= columnIndex;
}
return value;
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return Integer.class;
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
if (columnIndex == 0) {
if (aValue instanceof Integer) {
values.set(rowIndex, (int)aValue);
fireTableRowsUpdated(rowIndex, rowIndex);
}
}
}
}