Search code examples
javaswingjtableswingworkerthread-sleep

Dynamically adding rows to JTable - Why do they appear at once?


In the example, I'm seeking to add a table to my GUI and then dynamically add rows to it (to show the progress). What I don't understand is why all the rows are appearing at once. I mean, the the table's changing, isn't it? Can someone please give me an explanation?

import java.awt.Component;

public class Main {
  public static void main(String[] args) {
    // Show GUI
    java.awt.EventQueue.invokeLater(new Runnable() {
      public void run() {
        GUI gui = new GUI();
        gui.setVisible(true);

        DefaultTableModel model = new DefaultTableModel(
          new String[] { "Column 1", "Column 2" }, 0);
        JTable table = new JTable(model);

        gui.add(table);
        gui.validate();

        for (int i = 0; i < 10; i++) {
          System.out.println("Row " + i);
          model
            .addRow(new String[] { "Row", String.valueOf(i) });
          // model.fireTableDataChanged();

          try {
            Thread.sleep(250);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }

        }
      }
    });
  }
}

class GUI extends JFrame {
  private static final long serialVersionUID = 1L;

  public GUI() {
    setTitle("GUI");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setBounds(100, 100, 350, 100);
    setLocationRelativeTo(null);

    JPanel cp = new JPanel();
    cp.setBorder(new EmptyBorder(10, 10, 10, 10));
    setContentPane(cp);
  }
}

Solution

  • As pointed out by kleopatra and peeskillet, my initial example suffered from a stupid mistake. It's worth noting that peeskillet and I were following different approaches, though. In my example, the columns meant to represent connection attempts (more or less) that can take an unknown amount of time and that can actually fail (in that case, and only in that case, the next column would come into play and so on). Therefore, it wouldn't have made sense for me to add the rows at once (which was probably what made my example look weird to peeskillet). I've solved the task using a SwingWorker. As pointed out by kleopatra, there was a another mistake, which is now fixed. Here's my code:

    package SwingWorkerExampleCopy;
    
    import java.util.List;
    import java.util.Random;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.JScrollPane;
    import javax.swing.JTable;
    import javax.swing.SwingWorker;
    import javax.swing.border.EmptyBorder;
    import javax.swing.table.DefaultTableModel;
    import java.awt.BorderLayout;
    
    public class SwingWorkerExampleCopy {
      public static void main(String[] args) {
        // Show GUI
        java.awt.EventQueue.invokeLater(new Runnable() {
          @Override
          public void run() {
            GUI gui = new GUI();
    
            DefaultTableModel tableModel = new DefaultTableModel();
    
            // Use a SwingWorker
            Worker worker = new Worker(tableModel);
            worker.execute();
    
            JTable table = new JTable(tableModel);
            table.setEnabled(false);
            // table.setTableHeader(null);
    
            JScrollPane scrollPane = new JScrollPane(table);
            gui.getContentPane()
              .add(scrollPane, BorderLayout.CENTER);
    
          }
        });
      }
    }
    
    class GUI extends JFrame {
      private static final long serialVersionUID = 1L;
    
      public GUI() {
        setTitle("GUI");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 350, 400);
        setLocationRelativeTo(null);
        setVisible(true);
    
        JPanel contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(10, 10, 10, 10));
        contentPane.setLayout(new BorderLayout(0, 0));
    
        setContentPane(contentPane);
      }
    }
    
    class Worker extends SwingWorker<DefaultTableModel, Object[]> {
    
      private final static int  numRows = 10;
      private final static int  numCols = 10;
    
      private DefaultTableModel model;
    
      Worker(DefaultTableModel model) {
        this.model = model;
        model.setColumnCount(numCols);
      }
    
      @Override
      protected DefaultTableModel doInBackground() throws Exception {
        // Add row
        for (int row = 0; row < numRows; row++) {
          // Build columns
          for (int col = 0; col < numCols; col++) {
            if (col == 0) {
              publish(new Object[] { new String("Row " + row), row,
                col });
            } else {
              // Simulate a slow source
              Thread
                .sleep(new Random().nextInt((250 - 50) + 1) + 50);
    
              Boolean isSuccessful = false;
    
              // Simulate a return value
              if (new Random().nextBoolean()) {
                isSuccessful = true;
              }
    
              publish(new Object[] {
                new String((isSuccessful == true ? "x" : "o")), row,
                col });
    
              if (isSuccessful == true) {
                break;
              }
            }
          }
        }
    
        return model;
      }
    
      @Override
      protected void process(List<Object[]> chunks) {
        for (Object[] chunk : chunks) {
          // chunk[0]: cell value
          // chunk[1]: number
          // chunk[2]: column
          if ((int) chunk[2] == 0) {
            Object[] row = new Object[numCols];
            row[0] = (Object) chunk[0];
            model.addRow(row);
          } else {
            model.setValueAt((Object) chunk[0], (int) chunk[1],
              (int) chunk[2]);
          }
        }
      }
    }