Nivlem
Nivlem

Reputation: 141

Java swing : update a JTable every time a loop adds a row

In this code, perfs is a String[] and model is a DefaultTableModel associated to a JTable.

 for(int i =0; i<100000; i++){
    model.addRow(perfs);
 }

I would like the rows to appear as they are added, but they only appear once the loop is over. Could this have something to do with te fact the table is in a JScrollPane ?

Here's the full code in case it might be useful

import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class gwy implements ActionListener{

private JFrame frame;
private JPanel panel;
private JButton button;
String[] nomsColonnes = {"INSTANCE","Solvabilité","Profondeur(ms)","Largeur(ms)", "Heur. basique(ms)", "Heur. opérations variées", "Heur. distances au carré"};
private DefaultTableModel model = new DefaultTableModel(nomsColonnes, 0);
private JTable tableResultats = new JTable(model);
JScrollPane scrollPane;

public gwy(){
    frame = new JFrame();

    button = new JButton("Résoudre");
    button.addActionListener(this);

    scrollPane = new JScrollPane(tableResultats);
    tableResultats.setFillsViewportHeight(true);

    panel = new JPanel();
    panel.setSize(70,50);
    panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10,10));
    panel.setLayout(new BoxLayout(panel,BoxLayout.PAGE_AXIS));

    panel.add(button);
    panel.add(scrollPane);

    frame.add(panel, BorderLayout.CENTER);

    //frame.setSize(500,500);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setTitle("GUI");
    frame.pack();
    frame.setVisible(true);
}

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

@Override
public void actionPerformed(ActionEvent e) {
    for(int i =0; i<10000000; i++){
        model.addRow(nomsColonnes);
    }
}
}

Upvotes: 0

Views: 405

Answers (1)

MadProgrammer
MadProgrammer

Reputation: 347332

The problem is, Swing is single threaded. This means that until your loops completes, it's blocking the Event Dispatching Thread from processing any new events, which would otherwise update the UI.

Swing is also not thread safe, meaning you should not update the UI, or any state the UI depends on, from out the context of the Event Dispatching Thread.

See Concurrency in Swing for more details.

A common way to achieve this would be to use a SwingWorker which can be used to create new rows in a seperate thread, but which can sync the updates back to the Event Dispatching Thread safely.

See Worker Threads and SwingWorker for more details

Runnable example

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingWorker;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;

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

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {
        private MyTableModel model;
        public TestPane() {
            setLayout(new BorderLayout());
            model = new MyTableModel();

            JTable table = new JTable(model);
            model.addTableModelListener(new TableModelListener() {
                @Override
                public void tableChanged(TableModelEvent e) {
                    if (e.getType() == TableModelEvent.INSERT) {
                        int row = e.getFirstRow();
                        table.scrollRectToVisible(table.getCellRect(row, 0, true));
                    }
                }
            });

            add(new JScrollPane(table));

            JButton populate = new JButton("Populate");
            populate.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    populate.setEnabled(false);
                    PopulateWorker worker = new PopulateWorker(model);
                    worker.execute();
                }
            });

            add(populate, BorderLayout.SOUTH);
        }
    }

    public class PopulateWorker extends SwingWorker<Void, MyRowData> {

        private MyTableModel model;

        public PopulateWorker(MyTableModel model) {
            this.model = model;
        }

        public MyTableModel getModel() {
            return model;
        }

        @Override
        protected void process(List<MyRowData> chunks) {
            for (MyRowData row : chunks) {
                model.addRow(row);
            }
        }

        @Override
        protected Void doInBackground() throws Exception {
            for (int index = 0; index < 10_000; index++) {
                publish(new MyRowData(Integer.toString(index)));
                // Decrease this to make it faster
                Thread.sleep(125);
            }
            return null;
        }

    }

    public class MyRowData {
        private String identifer;

        public MyRowData(String identifer) {
            this.identifer = identifer;
        }

        public String getIdentifer() {
            return identifer;
        }

    }

    public class MyTableModel extends AbstractTableModel {
        private String[] columnNames = new String[]{"INSTANCE"};
        private List<MyRowData> data = new ArrayList<>(32);

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

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

        @Override
        public String getColumnName(int column) {
            return columnNames[column];
        }

        @Override
        public Class<?> getColumnClass(int columnIndex) {
            return String.class;
        }

        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            MyRowData row = data.get(rowIndex);
            switch (columnIndex) {
                case 0: return row.getIdentifer();
            }
            return null;
        }

        public void addRow(MyRowData row) {
            data.add(row);
            fireTableRowsInserted(data.size() - 1, data.size() - 1);
        }
    }
}

nb:: There is a Thread.sleep inside the doInBackground method of the SwingWorker, this is very important. If you remove it, the worker will complete before the UI get's updated. Instead, you can modify the Thread.sleep to increase or decrease the speed at which the updates occur. Just beware, if it's low enough, you might end up with multiple rows appearing at the same time, depending on the thread load.

Upvotes: 2

Related Questions