Skip to Main Content

Java SE (Java Platform, Standard Edition)

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

Interested in getting your voice heard by members of the Developer Marketing team at Oracle? Check out this post for AppDev or this post for AI focus group information.

Efficient JTable updates

843806Oct 13 2008 — edited Oct 13 2008
RE: Java 1.5 Update 16

All,

I'm sure I'm not covering new ground here, but my web searches are not resulting in any useful information. I'm trying to figure out how to efficiently update a JTable when the underlying data model changes. I'm aware of the AbstractTableModel and API such as fireTableCellUpdated. However, the problem I've observed is that the JTable code seems to be redrawing a rect around all the cells that have changed during a particular EventQueue loop. So, let's say just two cells have changed 0:0 and X:Y where X = numRows and Y = numCols. Even though only two cells have been updated (i.e. I have called fireTableCellUpdated twice in succession on 0:0 and X:Y), in my testing, the entire table is redrawn, because the two cells in question are the upper left cell and the lower right cell in the table. The JTable redrawing code seems to draw one big rect around both cells and ends up updated everything in between. If I break up the calls to fireTableCellUpdated to separate passes through the EventQueue, then just the corners are updated. However, in the case of my data model and rapid firing of updates, I cannot break things up this way. Does anyone know if there's any better way to approach this? Any advice would be much appreciated.

Comments

camickr
I have never noticed this problem.

Search the forum for my "Table Thread" (without the spaces) example.

If you need further help then you need to create a [Short, Self Contained, Compilable and Executable, Example Program (SSCCE)|http://homepage1.nifty.com/algafield/sscce.html], that demonstrates the incorrect behaviour.

Don't forget to use the Code Formatting Tags so the posted code retains its original formatting. That is done by selecting the code and then clicking on the "Code" button above the question input area.
843806
I'm attaching code that demonstrates this issue. Note that the code purposely does not make use of the DefaultTableModel's setValueAt API. Use of this method would fire table cell updates. I'm updating the underlying data, and then manually firing the updates so that the issue manifests itself quite starkly when the app is run. In the default form, the entire table is updated. If you follow the comments and modify the code, you'll see the app run with only the upper left and lower right hand corner cells updating. Note also that I do not necessarily think this is a bug in JTable. You can imagine a somewhat inverted scenario where many interior cells are updated but not all. If JTable made a bunch of separate calls to draw small individual rects, that might end up being much less efficient than simply redrawing the whole table.
public class TableUpdateTest extends JFrame {

    public static void main(String[] args) {
        new TableUpdateTest();
    }

    private static final Random RANDOM = new Random();

    public static int randomIntInRange(int low, int high) {
        if (low == high) {
            return low;
        }

        if (low>=high) {
            throw new IllegalArgumentException("Cannot generate randomIntInRange because low " + low + " is not less than high " + high + ".");
        }

        int random;

        synchronized (RANDOM) {
            random = (Math.abs(RANDOM.nextInt()) % ((high+1) - low)) + low;
        }

        return random;
    }

    TableUpdateTest() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        final Vector<Vector<String>> data = new Vector<Vector<String>>();
        Vector<String> datum = new Vector<String>();
        datum.add("Suzanne");
        datum.add("Smith");
        datum.add("Suzie");
        data.add(datum);
        datum = new Vector<String>();
        datum.add("Joseph");
        datum.add("Bank");
        datum.add("Joey");
        data.add(datum);
        datum = new Vector<String>();
        datum.add("Ezra");
        datum.add("Pound");
        datum.add("Ezzie");
        data.add(datum);
        datum = new Vector<String>();
        datum.add("Gordon");
        datum.add("Lightfoot");
        datum.add("Gordo");
        data.add(datum);
        datum = new Vector<String>();
        datum.add("Thomas");
        datum.add("Pickering");
        datum.add("Tom");
        data.add(datum);
        datum = new Vector<String>();
        datum.add("Jerry");
        datum.add("Mathers");
        datum.add("Beaver");
        data.add(datum);
        Vector<String> columnNames = new Vector<String>();
        columnNames.add("First Name");
        columnNames.add("Last Name");
        columnNames.add("Nick Name");
        final DefaultTableModel dataModel = new DefaultTableModel(
                data,
                columnNames
        );

        new Timer(250, new ActionListener() {
            private boolean toggle;

            public void actionPerformed(ActionEvent e) {
                // Switch everyone's names around by manipulating
                // the underlying data structures and NOT using
                // the DefaultTableModel API which would fire an
                // update for every cell in the table.
                for (int i = 0, size = data.size(); i < size; i++) {
                    Vector<String> datum1 = data.get(i);
                    int otherIndex = randomIntInRange(0, size-1);
                    Vector<String> datum2 = data.get(otherIndex);
                    String lastName = datum1.get(0), firstName = datum1.get(1), nickName = datum1.get(2);
                    datum1.set(0, datum2.get(0));
                    datum1.set(1, datum2.get(1));
                    datum1.set(2, datum2.get(2));
                    datum2.set(0, firstName);
                    datum2.set(1, lastName);
                    datum2.set(2, nickName);
                }

                // Now that everyone is jumbled, fire updates to the
                // upper left and lower right hand corners only.

                // These two lines together cause the entire
                // table to be redrawn.
                dataModel.fireTableCellUpdated(0,0);
                dataModel.fireTableCellUpdated(data.size(),2);

                // Uncomment the code below and comment out the two lines
                // above, and watch just the corner cells update.
                /*if (toggle) {
                    dataModel.fireTableCellUpdated(0,0);
                } else {
                    dataModel.fireTableCellUpdated(data.size()-1,2);
                }
                toggle = !toggle;*/
            }
        }).start();

        JPanel panel = new JPanel(new BorderLayout());
        JTable table = new JTable();
        table.setModel(dataModel);
        JScrollPane scrollPane = new JScrollPane(table);
        panel.add(scrollPane);
        setContentPane(panel);
        setSize(350, 200);
        setLocation(300, 300);
        setVisible(true);
    }
}
camickr
Sorry, Jay I wasn't sure if you where suggesting to invoke paintImmediately or not, but it does work.
If JTable made a bunch of separate calls to draw small individual rects, that might end up being much less efficient than simply redrawing the whole table.
I reordered your code a bit an then used:
table.paintImmediately(table.getCellRect(0, 0, false));
table.paintImmediately(table.getCellRect(data.size()-1, 2, false));
// dataModel.fireTableCellUpdated(0,0);
// dataModel.fireTableCellUpdated(data.size(),2);
This method bypasses the RepaintManager.
1 - 3
Locked Post
New comments cannot be posted to this locked post.

Post Details

Locked on Nov 10 2008
Added on Oct 13 2008
4 comments
3,134 views