Confirmed as a bug on JTable can\'t format given Object as Number when columnClass is Double (bug ID: 7051636). Feel free to vote for it, or if if you have
The problem is not the double but something else. As you can see in the stack trace, the table has special support for double values (javax.swing.JTable$DoubleRenderer
).
The problem us that the value passed to setValue()
is not a Double
but something else.
reall problem is noting to do with previous debate, there is plain vanilla (compare my missing English skills), are you think that that's really Bug, or if I showed ViceVersaView, maybe then somone can be able to comment that chains for JTable + TableModel + Comparator (TableRowSorter for JTable API) in English language, eeeeeerghhh really my bad in this case
import java.awt.*;
import java.awt.event.*;
import java.util.Date;
import javax.swing.*;
import javax.swing.table.*;
public class ViceVersaBugFromTableModelAndComparator {
private String[] columnNames = {"String", "Integer", "Boolean", "Double", "Date"};
private Object[][] data = {
{"aaa", 12, true, .15, new Date()},
{"bbb", 5, false, 100.01, new Date()},
{"CCC", 92, true, 15.2, new Date()},
{"DDD", 0, false, 10.80, new Date()},
{"abc", 5, true, 4.11, new Date()},
{"bae", 31, false, 100.01, new Date()},
{"CAX", 27, true, 2.3, new Date()},
{"AXD", 4, false, 50.00, new Date()},
{"abc", 3, true, 1.5, new Date()},
{"bae", 5, false, 1000, new Date()}, //java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.String
//{"bae", 5, false, 1000.0, new Date()}, //un-comment for correctness
{"CAX", 2, true, 21.7, new Date()},
{"AXD", 2, false, 5.30, new Date()}
};
private TableModel model = new DefaultTableModel(data, columnNames) {
private static final long serialVersionUID = 1L;
@Override
public Class<?> getColumnClass(int column) {
return String.class;// again java.lang.ClassCastException
//return getValueAt(0, column).getClass(); //un-comment for correctness
}
};
private JTable table = new JTable(model);
private JTableHeader header;
static class TestTableRowSorter extends TableRowSorter<TableModel> {
public TestTableRowSorter(TableModel m) {
super(m);
}
@Override
public void toggleSortOrder(int column) {
}
public void wrapToggleSortOrder(int column) {
super.toggleSortOrder(column);
}
}
private Timer timer = new Timer(400, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("single");
JTable table = header.getTable();
RowSorter sorter;
if (pt != null && table != null && (sorter = table.getRowSorter()) != null) {
int columnIndex = header.columnAtPoint(pt);
if (columnIndex != -1) {
columnIndex = table.convertColumnIndexToModel(columnIndex);
((TestTableRowSorter) sorter).wrapToggleSortOrder(columnIndex);
}
}
}
});
private Point pt;
public JComponent makeUI() {
timer.setRepeats(false);
table.setRowSorter(new TestTableRowSorter(model));
header = table.getTableHeader();
header.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(final MouseEvent e) {
if (timer.isRunning() && !e.isConsumed() && e.getClickCount() > 1) {
System.out.println("double");
pt = null;
timer.stop();
} else {
pt = e.getPoint();
timer.restart();
}
}
});
JPanel p = new JPanel(new BorderLayout());
p.add(new JScrollPane(table));
return p;
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.getContentPane().add(new ViceVersaBugFromTableModelAndComparator().makeUI());
f.setSize(820, 240);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
The thing is: when you format the Double value, it becomes a string, you can't return it back cause the column only allows Double.class. So if you change the Double.class to String.class, it will work.
how did Walter Laan
says in his thread
Never give up! Never surrender!
EDIT: I can't resist, but due to my poor English I dare not to commenting why, where and how is that possible, nor works correctly, for confirmations I added Rob's two (little bit) modified class for TableColumnRendering ...,
import java.awt.EventQueue;
import java.math.RoundingMode;
import java.text.*;
import java.util.*;
import javax.swing.*;
import javax.swing.RowSorter.SortKey;
import javax.swing.SwingConstants;
import javax.swing.table.*;
public class TableExample extends JFrame {
public static final Object EMPTY_ROW = "";
private static final long serialVersionUID = 1L;
private JTable tab;
private Calendar cal;
private Date dateWithOutTime = new java.util.Date();
private SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy");//standard continental EU date format
public class EmptyRowComparator<COLUMN_TYPE extends Comparable<COLUMN_TYPE>> implements Comparator<Object> {//extends RuleBasedCollator{
private TableRowSorter<? extends AbstractTableMod> sorter;
private int column;
public EmptyRowComparator(TableRowSorter<? extends AbstractTableMod> sorter, int col) throws ParseException {
this.sorter = sorter;
this.column = col;
}
private int getSortOrder() {
SortOrder order = SortOrder.ASCENDING;
for (SortKey sortKey : sorter.getSortKeys()) {
if (sortKey.getColumn() == this.column) {
order = sortKey.getSortOrder();
break;
}
}
return order == SortOrder.ASCENDING ? 1 : -1;
}
@Override
public int hashCode() {
return 0;
}
@Override
@SuppressWarnings("unchecked")
public int compare(Object arg0, Object arg1) {
boolean empty1 = arg0 == EMPTY_ROW;
boolean empty2 = arg1 == EMPTY_ROW;
if (empty1 && empty2) {
return 0;
} else if (empty1) {
return 1 * getSortOrder();
} else if (empty2) {
return -1 * getSortOrder();
}
return ((Comparable<COLUMN_TYPE>) (COLUMN_TYPE) arg0).compareTo((COLUMN_TYPE) arg1);
}
}
public class ConcreteTable extends AbstractTableMod {
private static final long serialVersionUID = 4672561280810649603L;
private String[] columnNames = {"Integer", "String", "Integer", "Double", "Boolean", "Double", "String", "Boolean", "Date"};
private Class<?>[] types = {Integer.class, String.class, String.class, String.class, String.class,
String.class, String.class, String.class, String.class};
private int minimumDisplayedRow;
public ConcreteTable() {
this.minimumDisplayedRow = 10;
this.datas = new ArrayList<ArrayList<Object>>();
for (int i = 0; i < this.minimumDisplayedRow; i++) {
this.addEmptyRow();
}
Random rnd = new Random();
for (int i = 0; i < 7; i++) {
ArrayList<Object> row = new ArrayList<Object>();
row.add(i);
row.add(((rnd.nextInt(25)) + "prova"));
row.add(rnd.nextInt(25));
row.add(rnd.nextInt(25) + 3.14);
row.add((i % 2 == 0) ? true : false);
row.add(rnd.nextInt(25) + 3.14);
row.add(((rnd.nextInt(25)) + "prova"));
row.add((i % 2 == 0) ? false : true);
cal = Calendar.getInstance();
cal.add(Calendar.DATE, -rnd.nextInt(25));
dateWithOutTime = cal.getTime();
String nullTimeForDateString = sdf.format(dateWithOutTime);
try {
dateWithOutTime = sdf.parse(nullTimeForDateString);
} catch (ParseException ex) {
}
row.add(dateWithOutTime);
this.addRow(row);
}
}
@Override
public String getColumnName(int col) {
System.out.println("getColumnName " + col + " = " + columnNames[col]);
return columnNames[col];
}
@Override
protected Class<?>[] getTypeArray() {
return this.types;
}
@Override
protected ArrayList<Integer> getKeysColumnIndex() {
ArrayList<Integer> keys = new ArrayList<Integer>();
keys.add(0);
return keys;
}
@Override
public boolean isCellEditable(int row, int col) {
System.out.println("isCellEditable row " + row + " col " + col);
if (col == 1) {
System.out.println("TRUE");
return true;
}
return false;
}
@Override
protected Object getGeneratedKeys(int col) {
if (col != 0) {
return null;
}
return new Integer(this.rowNumber);
}
@Override
protected int getMinimumDisplayedRow() {
return this.minimumDisplayedRow;
}
}
public abstract class AbstractTableMod extends AbstractTableModel {
private static final long serialVersionUID = 1L;
protected ArrayList<ArrayList<Object>> datas;
protected int rowNumber = 0;
protected abstract Class<?>[] getTypeArray();
protected abstract ArrayList<Integer> getKeysColumnIndex();
protected abstract Object getGeneratedKeys(int col);
protected abstract int getMinimumDisplayedRow();
public int getRowCount() {
return this.datas.size();
}
@Override
public int getColumnCount() {
return this.getTypeArray().length;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
if (rowIndex >= this.rowNumber) {
return EMPTY_ROW;
}
try {
ArrayList<Object> row = this.datas.get(rowIndex);
if (row == null) {
return null;
}
Object obj = row.get(columnIndex);
return obj;
} catch (IndexOutOfBoundsException e) {
return null;
}
}
@Override
public void setValueAt(Object value, int row, int col) {
Class<? extends Object> targetColClass = this.getColumnClass(col);
if (!targetColClass.isInstance(value)) {
return;
}
if (value instanceof String) {
String stringVal = (String) value;
if (stringVal.compareTo("") == 0) {
return;
}
}
if (row >= this.rowNumber) {
ArrayList<Object> newRow = new ArrayList<Object>();
ArrayList<Integer> keysIndexList = this.getKeysColumnIndex();
for (int i = 0; i < this.getColumnCount(); i++) {
if (i == col) {
newRow.add(value);
} else if (keysIndexList.contains(i)) {
newRow.add(this.getGeneratedKeys(i));
} else {
newRow.add(EMPTY_ROW);
}
}
this.addRow(newRow);
} else {
this.datas.get(row).set(col, value);
}
this.fireTableCellUpdated(row, col);
}
@Override
@SuppressWarnings("unchecked")
public Class<? extends Object> getColumnClass(int c) {
if (c > this.getTypeArray().length - 1) {
return null;
} else {
return this.getTypeArray()[c];
}
}
public void addEmptyRow() {
ArrayList<Object> emptyRow = new ArrayList<Object>();
for (int i = 0; i < this.getTypeArray().length; i++) {
emptyRow.add(EMPTY_ROW);
}
this.datas.add(emptyRow);
}
public void addRow(ArrayList<Object> row) {
Object[] types = this.getTypeArray();
if (types.length != row.size()) {
return;
}
for (int i = 0; i < row.size(); i++) {
Class<? extends Object> targetColClass = this.getColumnClass(i);
Object rowItem = row.get(i);
}
this.datas.add(this.rowNumber, row);
this.rowNumber++;
if (this.rowNumber < this.getMinimumDisplayedRow()) {
this.datas.remove(this.datas.size() - 1);
}
this.fireTableRowsInserted(this.rowNumber, this.rowNumber);
}
}
public TableExample() {
super("JTable example");
getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
ConcreteTable model = new ConcreteTable();
tab = new JTable(model);
TableRowSorter<ConcreteTable> sorter = new TableRowSorter<ConcreteTable>(model);
try {
sorter.setComparator(0, new EmptyRowComparator<Integer>(sorter, 0));
sorter.setComparator(1, new EmptyRowComparator<String>(sorter, 1));
sorter.setComparator(2, new EmptyRowComparator<String>(sorter, 2));
sorter.setComparator(3, new EmptyRowComparator<String>(sorter, 3));
sorter.setComparator(4, new EmptyRowComparator<String>(sorter, 4));
sorter.setComparator(5, new EmptyRowComparator<String>(sorter, 5));
sorter.setComparator(6, new EmptyRowComparator<String>(sorter, 6));
sorter.setComparator(7, new EmptyRowComparator<String>(sorter, 7));
sorter.setComparator(8, new EmptyRowComparator<String>(sorter, 8));
} catch (ParseException e) {
e.printStackTrace();
}
tab.setRowSorter(sorter);
JScrollPane table = new JScrollPane(tab);
this.getContentPane().add(table);
this.setSize(800, 400);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setRenderers();
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
setVisible(true);
}
});
//TableExample tableExample = new TableExample();
}
public void setRenderers() {
TableColumnModel m = tab.getColumnModel();
//"Integer", "String", "Interger", "Double", "Boolean", "Double", "String", "Boolean", "Date"
m.getColumn(0).setCellRenderer(NumberRenderer.getIntegerRenderer());
m.getColumn(2).setCellRenderer(NumberRenderer.getIntegerRenderer());
m.getColumn(3).setCellRenderer(NumberRenderer.getDoubleRenderer5());
m.getColumn(5).setCellRenderer(NumberRenderer.getDoubleRenderer3());
m.getColumn(8).setCellRenderer(FormatRenderer.getDateRenderer());
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
TableExample tableExample = new TableExample();
}
});
TableExample tableExample = new TableExample();
}
}
class FormatRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = 1L;
private Format formatter;
private static DateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy");//standard continental EU date format
FormatRenderer(Format formatter) {
this.formatter = formatter;
}
@Override
public void setValue(Object value) {
try {
if ((value != null)) {
if ((value instanceof Number) || (value instanceof Date)) {
setHorizontalAlignment(SwingConstants.RIGHT);
value = formatter.format(value);
}
}
} catch (IllegalArgumentException e) {
}
super.setValue(value);
}
public static FormatRenderer getDateRenderer() {
return new FormatRenderer(dateFormat);
}
}
class NumberRenderer extends FormatRenderer {
private static final long serialVersionUID = 1L;
private static Number numberValue;
private static NumberFormat nf;
NumberRenderer(NumberFormat formatter) {
super(formatter);
setHorizontalAlignment(SwingConstants.RIGHT);
}
public static NumberRenderer getIntegerRenderer() {
return new NumberRenderer(NumberFormat.getIntegerInstance());
}
public static NumberRenderer getDoubleRenderer3() {
nf = NumberFormat.getNumberInstance();
nf.setMinimumFractionDigits(3);
nf.setMaximumFractionDigits(3);
nf.setRoundingMode(RoundingMode.HALF_UP);
return new NumberRenderer(nf);
}
public static NumberRenderer getDoubleRenderer5() {
nf = NumberFormat.getNumberInstance();
nf.setMinimumFractionDigits(5);
nf.setMaximumFractionDigits(5);
nf.setRoundingMode(RoundingMode.HALF_UP);
return new NumberRenderer(nf);
}
}