4 Replies Latest reply on Oct 13, 2008 11:11 PM by camickr

    Efficient JTable updates

      RE: Java 1.5 Update 16


      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.
        • 1. Re: Efficient JTable updates
          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.
          • 2. Re: Efficient JTable updates
            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() {
                    final Vector<Vector<String>> data = new Vector<Vector<String>>();
                    Vector<String> datum = new Vector<String>();
                    datum = new Vector<String>();
                    datum = new Vector<String>();
                    datum = new Vector<String>();
                    datum = new Vector<String>();
                    datum = new Vector<String>();
                    Vector<String> columnNames = new Vector<String>();
                    columnNames.add("First Name");
                    columnNames.add("Last Name");
                    columnNames.add("Nick Name");
                    final DefaultTableModel dataModel = new DefaultTableModel(
                    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.
                            // Uncomment the code below and comment out the two lines
                            // above, and watch just the corner cells update.
                            /*if (toggle) {
                            } else {
                            toggle = !toggle;*/
                    JPanel panel = new JPanel(new BorderLayout());
                    JTable table = new JTable();
                    JScrollPane scrollPane = new JScrollPane(table);
                    setSize(350, 200);
                    setLocation(300, 300);
            • 3. Re: Efficient JTable updates
              As you say, I don't believe this is a bug either. As this notes,
              if multiple calls to repaint() occur on a component or any of its Swing ancestors before the repaint request is processed, those multiple requests may be collapsed into a single call back to paintImmediately() on the topmost Swing component on which repaint() was invoked.

              You do have the option of providing a custom RepaintManager; I believe you could accomplish what you want with that.

              You may also be interested in this on customizing the repainting of frequently updated JTables.
              • 4. Re: Efficient JTable updates
                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.