Updating the contents of a JTable from an external source

Hi.

I'm working on a JTable. The contents of that table are gotten from a source somewhere. They can be updated by an external source at any time. Right now I have it working with the String values easily. Each row in the table has a TableRow class associated with it which contains a bunch of TableCells. This is how TableCell is implemented:

publicclass TableCell{

//what the cell says.

String data;

public TableCell(String data){

this.data = data;

}

publicvoid setData(String newData){

data = newData;

}

public String getData(){

return data;

}

public String toString(){

return data;

}

}

When a cell changes, the TableRow class gets a notification of the change and the new value and calls setData() on the TableCell and then updateUI() on the table.

Which works, as long as what is in the objects is Strings....

I have two columns however that are Booleans. There is no setValue() in the Boolean class, and I can't overwrite it to make one since it is a final class. Setting booleanNodeCell = new Boolean(true) doesn't work. Doing the same as the TableCell class except instead of having toString having a boolean getValue() method doesn't work. If I were to return "true" or "false" in the toString method, my table wouldn't be rendered with checkboxes, which is what I would like.

Any help would be greatly appreciated.

Thanks!

[2075 byte] By [kasandracorvinaa] at [2007-11-27 3:43:17]
# 1

First, it is not a good idea to call updateUI() on the table (this should be called only if the look and feel did changed), call repaint() instead.

The problem with boolean values can be solved by making your TableCell class have an Object for the data instead of a String:

public class TableCell {

//what the cell says.

Object data;

public TableCell(Object data) {

this.data = data;

}

public void setData(Object newData) {

data = newData;

}

public Object getData() {

return data;

}

public String toString() {

if(data == null) return "";

return data.toString();

}

}

HansBickela at 2007-7-12 8:46:56 > top of Java-index,Desktop,Core GUI APIs...
# 2

Hello, HansBickel.

Thanks for your insight on calling repaint() instead of updateUI(). One of my co-workers told me to use updateUI() "because it solves every problem".

The code provided give me a java.lang.ClassCastException. The reason for this is because I'm declaring the column to be of type Boolean because I want the Table to render the cells as checkboxes.

Message was edited by:

kasandracorvina

kasandracorvinaa at 2007-7-12 8:46:56 > top of Java-index,Desktop,Core GUI APIs...
# 3
Removing the overridden public Class getColumnClass(int index){} method from my TableModel gets rid of the ClassCastException, however, now my table says "true" and "false" instead of displaying checkboxes that I can click.
kasandracorvinaa at 2007-7-12 8:46:56 > top of Java-index,Desktop,Core GUI APIs...
# 4

> Removing the overridden public Class

> getColumnClass(int index){} method from my TableModel

> gets rid of the ClassCastException, however, now my

> table says "true" and "false" instead of displaying

> checkboxes that I can click.

You get checkboxes by providing a table cell renderer, here is an example class:

class BooleanRenderer extends JCheckBox implements TableCellRenderer {

public BooleanRenderer() {

super();

setHorizontalAlignment(JLabel.CENTER);

}

public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)

{

if(isSelected) {

setForeground(table.getSelectionForeground());

setBackground(table.getSelectionBackground());

}

else {

setForeground(table.getForeground());

setBackground(table.getBackground());

}

setSelected(Boolean.TRUE.equals(value));

return this;

}

}

You register the renderer with a table column like this:

table.getColumnModel().getColumn(0).setCellRenderer(new BooleanRenderer());

HansBickela at 2007-7-12 8:46:56 > top of Java-index,Desktop,Core GUI APIs...
# 5

> Thanks for your insight on calling repaint() instead

> of updateUI(). One of my co-workers told me to use

> updateUI() "because it solves every problem".

it sure does solve a lot of problems, but it also eats up a lot of resources.

the correct way to update a table is to use the AbstractTableModel's fireXXX method. that way the table will refreh only the parts, that need refreshing.

> The code provided give me a

> java.lang.ClassCastException. The reason for this is

> because I'm declaring the column to be of type

> Boolean because I want the Table to render the cells

> as checkboxes.

what do you mean by "I'm declaring the column to be of type Boolean"? columns as such don't have a class type, each cell could be of a different class.

in general, what you want is already implemented in Swing and you don't need a new/separate renderer as HansBickel suggests (even so he is quite right). all you have to do is to make sure, that the getColumnClass of your AbstractTableModel returns an object of type Boolean and the default table cell renderer will show it as a check box.

there are also default renderer for Integer and String.

thomas

kloebera at 2007-7-12 8:46:56 > top of Java-index,Desktop,Core GUI APIs...
# 6

override the getColumnClass method in your TableModel as follows,

public Class getColumnClass(int columnIndex) {

if(columnindex == theBooleanColumn) {

return Boolean.class;

}

return Object.class;

}

where theBooleanColumn is the index of the Column which has the Boolean values.

Regards.

Milo_DCUa at 2007-7-12 8:46:56 > top of Java-index,Desktop,Core GUI APIs...
# 7

Folks'es

this is all way too complicated! it's much easier than that:import javax.swing.*;

import javax.swing.table.*;

import java.awt.*;

import java.awt.event.*;

public class TableDemo2 extends JFrame {

private JTable table;

public TableDemo2() {

super("TableDemo2");

MyTableModel myModel = new MyTableModel();

table = new JTable(myModel);

table.setPreferredScrollableViewportSize(new Dimension(500, 70));

//Create the scroll pane and add the table to it.

JScrollPane scrollPane = new JScrollPane(table);

//Add the scroll pane to this window.

getContentPane().add(scrollPane, BorderLayout.CENTER);

}

class MyTableModel extends AbstractTableModel {

final String[] columnNames = {"First Name",

"Last Name",

"Sport",

"# of Years",

"Vegetarian"};

final Object[][] data = {

{"Mary", "Campione",

"Snowboarding", new Integer(5), new Boolean(false)},

{"Alison", "Huml",

"Rowing", new Integer(3), new Boolean(true)},

{"Kathy", "Walrath",

"Chasing toddlers", new Integer(2), new Boolean(false)},

{"Mark", "Andrews",

"Speed reading", new Integer(20), new Boolean(true)},

{"Angela", "Lih",

"Teaching high school", new Integer(4), new Boolean(false)}

};

public int getColumnCount() { return columnNames.length; }

public int getRowCount() { return data.length; }

public String getColumnName(int col) { return columnNames[col]; }

public Object getValueAt(int row, int col) { return data[row][col]; }

public Class getColumnClass(int c) { return getValueAt(0, c).getClass();

}

}

public static void main(String[] args) {

TableDemo2 frame = new TableDemo2();

frame.pack();

frame.setVisible(true);

}

}

simply replace the columnsNames and data with your column and data object(s) and your all set.

thomas

kloebera at 2007-7-12 8:46:56 > top of Java-index,Desktop,Core GUI APIs...
# 8
And what do you do when it's time to update data?
HansBickela at 2007-7-12 8:46:56 > top of Java-index,Desktop,Core GUI APIs...
# 9

> And what do you do when it's time to update data?

dead simple:

1. put the new data into your data array(list)

2. fire the appr. table event: ie

fireTableCellUpdated(int row, int column)

fireTableChanged(TableModelEvent e)

fireTableDataChanged()

fireTableRowsDeleted(int firstRow, int lastRow)

fireTableRowsInserted(int firstRow, int lastRow)

fireTableRowsUpdated(int firstRow, int lastRow)

fireTableStructureChanged()

which one is appropriate depends on the amount of changes you've made. details are in the API Doc for AbstractTableModel.

thomas

kloebera at 2007-7-12 8:46:56 > top of Java-index,Desktop,Core GUI APIs...
# 10

just for the sheer fun of it (and since you asked :)

notice, that numbers are automatically right aligned, text left, and the check box is centered!

the important bits to see the changes in the data are the 2 fireTableXXX methods in the data generator!import java.util.*;

import javax.swing.*;

import javax.swing.table.*;

public class TableDemo2 extends JFrame {

public static final int MAX_ROWS = 50;

public static final int MAX_COLS = 3;

private String columnNames[] = { "Text", "Number", "Boolean" };

private ArrayList<TableRow> data = new ArrayList<TableRow>();

public TableDemo2() {

super("TableDemo2");

setDefaultCloseOperation(EXIT_ON_CLOSE);

JTable table = new JTable(new AbstractTableModel() {

public int getColumnCount() { return columnNames.length; }

public int getRowCount() { return data.size(); }

public String getColumnName(int col) { return columnNames[col]; }

public Class getColumnClass(int col) { return getValueAt(0, col).getClass(); }

public Object getValueAt(int row, int col) { return data.get(row).getData(col); }

});

//Add the scroll pane to this window.

getContentPane().add(new JScrollPane(table));

// start data generator

new Thread(new DataGenerator(table, data)).start();

}

public static void main(String[] args) {

TableDemo2 frame = new TableDemo2();

frame.pack();

frame.setVisible(true);

frame.setLocationRelativeTo(null);

}

}

class TableRow {

TableCell cells[] = new TableCell[3];

public TableRow(Object value, int col) {

this(" ", .0, false);

setData(value, col);

}

public TableRow(Object c0, Object c1, Object c2) {

cells[0] = new TableCell(c0);

cells[1] = new TableCell(c1);

cells[2] = new TableCell(c2);

}

public Object getData(int col) { return cells[col].getData(); }

public void setData(Object newValue, int col) { cells[col].setData(newValue); }

}

class TableCell {

//what the cell says.

Object data;

public TableCell(Object data) { this.data = data; }

public void setData(Object newData) { data = newData; }

public Object getData() { return data; }

public String toString() { return data.toString(); }

}

class DataGenerator implements Runnable {

private Random rnd = new Random(System.currentTimeMillis());

private JTable table;

private ArrayList<TableRow> data;

public DataGenerator(JTable aTable, ArrayList<TableRow> someData) {

table = aTable;

data = someData;

}

public void run() {

while (true) {

int row = rnd.nextInt(TableDemo2.MAX_ROWS);

int col = rnd.nextInt(TableDemo2.MAX_COLS);

Object newValue;

switch (col) {

case 0:

byte bytes[] = new byte[rnd.nextInt(20)];

rnd.nextBytes(bytes);

newValue = new String(bytes);

break;

case 1:

newValue = rnd.nextDouble();

break;

case 2:

newValue = rnd.nextBoolean();

break;

default:

newValue = "default";

break;

}

try {

data.get(row).setData(newValue, col);

((AbstractTableModel)table.getModel()).fireTableCellUpdated(row, col);

} catch (IndexOutOfBoundsException e) {

// a new row to the front

data.add(0, new TableRow(newValue, col));

((AbstractTableModel)table.getModel()).fireTableRowsInserted(0, 0);

// a new row at the back

//data.add(new TableRow(newValue, col));

//((AbstractTableModel)table.getModel()).fireTableRowsInserted(data.size(), data.size());

}

// to slow things down uncomment the following 2 lines

// try { Thread.currentThread().sleep(100); }

// catch(InterruptedException e) { return; }

}

}

}

Message was edited by:

kloeber

kloebera at 2007-7-12 8:46:56 > top of Java-index,Desktop,Core GUI APIs...
# 11

Thanks for the replies guys. I really appreciate the help so far. I think the problem isn't really understood.

> in general, what you want is already implemented in

> Swing and you don't need a new/separate renderer as

> HansBickel suggests (even so he is quite right). all

> you have to do is to make sure, that the

> getColumnClass of your AbstractTableModel

> returns an object of type Boolean and the default

> table cell renderer will show it as a check box.

>

Right, I know that. It did, originally, however...that gives me:

Exception in thread "AWT-EventQueue-0" java.lang.ClassCastException: MDS.GUI.Nodes.NodeTableCell

at org.jdesktop.swingx.JXTable$BooleanRenderer.getTableCellRendererComponent(Unknown Source)

at javax.swing.JTable.prepareRenderer(Unknown Source)

at org.jdesktop.swingx.JXTable.prepareRenderer(Unknown Source)

at javax.swing.plaf.basic.BasicTableUI.paintCell(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.paintChildren(Unknown Source)

at javax.swing.JComponent.paint(Unknown Source)

at javax.swing.JViewport.paint(Unknown Source)

at javax.swing.JComponent.paintChildren(Unknown Source)

at javax.swing.JComponent.paint(Unknown Source)

at javax.swing.JComponent.paintChildren(Unknown Source)

at javax.swing.JComponent.paint(Unknown Source)

at javax.swing.JComponent.paintChildren(Unknown Source)

at javax.swing.JComponent.paint(Unknown Source)

at javax.swing.JComponent.paintChildren(Unknown Source)

at javax.swing.JComponent.paint(Unknown Source)

at javax.swing.JComponent.paintChildren(Unknown Source)

at javax.swing.JComponent.paint(Unknown Source)

at javax.swing.JLayeredPane.paint(Unknown Source)

at javax.swing.JComponent.paintChildren(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)

public class NodeTableCell {

//what the cell says.

Object data;

public NodeTableCell(Object data) {

this.data = data;

}

public void setData(Object newData) {

data = newData;

}

public Object getData() {

return data;

}

public String toString() {

return data.toString();

}

}

public class NodeTableModel extends DefaultTableModel {

//.........snip................

public Class getColumnClass(int index) {

Class retVal = NodeTableCell.class;

String columnName = getColumnName(index);

if (columnName.contains(polling)) {

retVal = Boolean.class;

}

return retVal;

}

}

Sooo here is what is going on:

I have a MapNodeModel (just the name of the class) which has a lot of data associated with it, which I am displaying in a table form. For each MapNodeModel in the program (there could be thousands) a TableNodeView is created and registers with the MapNodeModel. The TableNodeView represents 1 row in the table. Each TableNodeView has a TableNodeCell associated with it that holds the data for each cell in the tables. I hope this is understandable....

When another part of the program changes the MapNodeModel (say changes it's name) an event is received at the NodeTableView, which calls setData(newData) on the appropriate NodeTableCell. The JTable is pointing to the NodeTableCell and sees the newData automatically.

Here is what the code is:

public class TableNodeView implements NodeView {

public NodeTableCell name;

public NodeTableCell conn; //THE BOOLEAN!

public TableNodeView(MapNodeModel model) {

this.model = model;

name = new NodeTableCell(model.getDeviceName());

conn = new NodeTableCell(new Boolean(model.getConnPoll()));

}

//Called when the MapNodeModel gets updated

public void modelChanged(MapNodeEvent evt) {

//checks to see what evt is updating first.

name.setValue(model.getDeviceName());

conn.setValue(new Boolean(model.getConnPoll()));

}

}

This is the code that throws the ClassCastException.

This also doesn't work.

public class TableNodeView implements NodeView {

public NodeTableCell name;

public NodeTableCell conn; //THE BOOLEAN!

public Boolean c;

public TableNodeView(MapNodeModel model) {

this.model = model;

name = new NodeTableCell(model.getDeviceName());

conn = new NodeTableCell(new Boolean(model.getConnPoll()));

c = (Boolean)connPoll.getData();

}

//Called when the MapNodeModel gets updated

public void modelChanged(MapNodeEvent evt) {

//checks to see what evt is updating first.

name.setValue(model.getDeviceName());

conn.setValue(new Boolean(model.getConnPoll()));

c = (Boolean)conn.getData();

}

}

This never gets updated in the table...

As you can see I'm really stuck here. :(

Thanks for all your help!

Message was edited by:

kasandracorvina

kasandracorvinaa at 2007-7-12 8:46:56 > top of Java-index,Desktop,Core GUI APIs...
# 12

As far as i understand it, the table has a BooleanRenderer (because the TableModel states that column xxx contains Boolean objects).

The ClassCastException is thrown because at least one value in the "Boolean" column is NOT of Boolean type. Because the table is fed from TableModel.getValueAt(int, int) my question is now: Does your table model override getValueAt(int, int) ?

It also would be from interest why you prefer a DefaultTableModel over an AbstractTableModel (that's what i'm using every day - and it requires you to implement getValueAt(int, int) so it's maybe easier to track which values actually feed the table).

HansBickela at 2007-7-12 8:46:56 > top of Java-index,Desktop,Core GUI APIs...
# 13

> As far as i understand it, the table has a

> BooleanRenderer (because the TableModel states that

> column xxx contains Boolean objects).

> The ClassCastException is thrown because at least one

> value in the "Boolean" column is NOT of Boolean type.

> Because the table is fed from

> TableModel.getValueAt(int, int) my question is now:

> Does your table model override getValueAt(int, int)

> ?

No, it does not.

>

> It also would be from interest why you prefer a

> DefaultTableModel over an AbstractTableModel (that's

> what i'm using every day - and it requires you to

> implement getValueAt(int, int) so it's maybe easier

> to track which values actually feed the table).

No reason.

kasandracorvinaa at 2007-7-12 8:46:56 > top of Java-index,Desktop,Core GUI APIs...
# 14

AbstractTable model is more efficient then DefaultTableModel, so i agree with Hans...

do i understand it right that you are changing the table's table model? if so, make sure to fire fireTableStructureChanged() which will reset the renderers/columns/etc.

to me it sounds like changing your data also changes the classes of a column...

thomas

kloebera at 2007-7-12 8:46:56 > top of Java-index,Desktop,Core GUI APIs...
# 15

When starting this thread you said

> Each row in the table has a TableRow class associated with it which contains a bunch of TableCells.

So you could override getValueAt(int, int) like this:

public Object getValueAt(int row, int column) {

// find the right TableRow object based on row argument

// then find the right TableCell object based on column argument

// then return the data stored in TableCell object

// you can also check the returned object's type:

Object retVal = tableCell.getData();

if(column == booleanColumn && retVal != null && !(retVal instanceof Boolean)) {

System.out.println("Row " + row + ": value is of type " + retVal.getClass.getName() + " - " + retVal);

// you know now where the false value is

// so you can prevent the ClassCastException

return Boolean.TRUE;

}

return retVal;

}

Good luck, Hans (here it is 18.30, i have to log out for today)

HansBickela at 2007-7-21 20:53:27 > top of Java-index,Desktop,Core GUI APIs...
# 16

Ok, I'm very sorry. The ClassCastException was thrown because I accidentally was placing NodeTableCells into all of the columns. That was a dumb mistake on my part.

> do i understand it right that you are changing the

> table's table model? if so, make sure to fire

> fireTableStructureChanged() which will reset

> the renderers/columns/etc.

Hrm... well not exactly.

The table starts out empty. Then the add gets called on it: (TableNodeView represents 1 row in the table)

protected void addRow(TableNodeView newRow) {

//each one of these is a NodeTableCell.

Object[] data = {newRow.identifier, newRow.address, newRow.name,

newRow.type, newRow.perfPoll.getData(), newRow.connPoll.getData(),

newRow.RSSI, newRow.SNR, newRow.lastPollTime, newRow.trap,

newRow.connTo};

tableMod.addRow(data);

}

Then the tableModel isn't really touched. All of the updates are called on the NodeTableCell class. For example

connPoll.setValue(new Boolean(Boolean.TRUE));

name.setValue("New Value");

Doesn't matter what I call, the getData() method isn't really called again, but the toString() (for Strings) is updated automatically.

*sigh* Thanks for all the help guys, I really really really appreciate it.

kasandracorvinaa at 2007-7-21 20:53:27 > top of Java-index,Desktop,Core GUI APIs...
# 17
In other words the problem is this:Open the tableChange a boolean in another part of the programThe table still shows the "Old" boolean value
kasandracorvinaa at 2007-7-21 20:53:27 > top of Java-index,Desktop,Core GUI APIs...
# 18

thta's why you need to fire an event when you change your data in another part of the program!

you need to distinguish between your data model and the view/control model. if you only change the data, how is the view model to know, that it needs updating. so using the AbstractTableModel makes this where easy:

- in the getValueAt() method you access your data(model)

- when you change the data somewhere you tell the table model via fireTableXXX to show the new stuff...

thomas

kloebera at 2007-7-21 20:53:27 > top of Java-index,Desktop,Core GUI APIs...
# 19
Thank you!!
kasandracorvinaa at 2007-7-21 20:53:27 > top of Java-index,Desktop,Core GUI APIs...