Question

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);
  }
}
Was it helpful?

Solution 2

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]);
      }
    }
  }
}

OTHER TIPS

Reiterating Kleopatra : Don't Sleep the EDT

You can instead use a javax.swing.Timer as seen in this answer

enter image description here


EDIT

I didn't want to mess with your code too much (just because it looks weird to me) but I changed it somewhat to add the Timer

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.Timer;
import javax.swing.border.EmptyBorder;
import javax.swing.table.DefaultTableModel;

public class Main {

    static JTable table;
    static GUI gui;
    static Processor p = null;

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

                p = new Processor() {

                    @Override
                    public void execute() {
                        final JTable table = new JTable(p.getTableModel());
                        final JScrollPane scrollPane = new JScrollPane(table);
                        gui.getContentPane().add(scrollPane, BorderLayout.CENTER);
                        gui.setLocationRelativeTo(null);
                        gui.setVisible(true);

                        Timer timer = new Timer(100, new ActionListener(){
                            public void actionPerformed(ActionEvent e) {
                                p.processRow();
                                table.scrollRectToVisible(table.getCellRect(table.getRowCount() - 1, 0, true));
                            }
                        });
                        timer.start();
                    }
                };
                p.execute();
            }
        });
    }
}

class GUI extends JFrame {

    private static final long serialVersionUID = 1L;

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

        JPanel contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(10, 10, 10, 10));
        contentPane.setLayout(new BorderLayout(0, 0));

        setContentPane(contentPane);
    }
}

interface Callback {

    void execute();
}

abstract class Processor implements Callback {

    private final String[] cols = {"COL", "COL", "COL", "COL", "COL"};
    private DefaultTableModel tableModel;
    int numRows;
    int numCols;
    int a, b, c, d, e;

    Processor() {
        a = 1; b = 2; c = 3; d = 4; e = 4;
        numRows = 1000;
        tableModel = new DefaultTableModel(cols, numCols);
    }

    public DefaultTableModel getTableModel() {
        return tableModel;
    }

    public void processRow() {
        tableModel.addRow(new Object[]{a, b, c, d, e});
        a++; b++; c++; d++; e++;
    }
}

Because while your code is running, no other events (such as repaint events) can execute - you're blocking the event thread until you're done.

You may be able to call repaint directly, but the UI will still be unresponsive to input while your code is running. You'd be better off running the loop in a separate worker thread, and using invokeLater or invokeAndWait to perform the updates to the UI when needed.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top