3

Good day,

I've read a few other stack overflow postings and other tutorials, but I can't get my GUI to update correctly after a button starts a long process. I've attached the full code of the problem that I am having. Notice if you run the code, the JList gets updated all at once at the end instead of every iteration of the for loop.

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;


public class theframe extends JFrame implements ActionListener
{
    private JList list;
    private DefaultListModel listmodel;
    private JButton start;

    public theframe()
    {
        listmodel = new DefaultListModel();
        list = new JList(listmodel);
        start = new JButton("Start");

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(500,500);
        setVisible(true);

        list.setPreferredSize(new Dimension(200,200));

        start.addActionListener(this);
        JPanel p = new JPanel();
        p.add(start);
        p.add(list);

        this.add(p);
    }

    public static void main(String[] args)
    {
        theframe frame = new theframe();

    }

    @Override
    public void actionPerformed(ActionEvent arg0)
    {
        if(arg0.getSource() == start)
        {
            for(int i=0;i<10;i++)
            {   
                SwingUtilities.invokeLater(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        // the JList should update one by one
                        listmodel.addElement("Start pushed ");
                    }
                });



                try
                {
                                    //This thread sleep simulates a long job
                    Thread.sleep(300);
                } 
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
        }
    }
}

Any help would be greatly appreciated.

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
Matthew
  • 3,886
  • 7
  • 47
  • 84

2 Answers2

4

The actionPerformed method will be called on the Event Dispatch Thread. Calling Thread.sleep on the EDT stops it from updating your GUI. Since your GUI cannot update, your JList will not repaint itself when items are added to it until after your loop exits.

You should probably be using a SwingWorker. (SwingWorker tutorial.)

Jeffrey
  • 44,417
  • 8
  • 90
  • 141
4

Your problem here is the fact that you are calling the invokeLater method already from the EDT (Event Dispatching Thread).

The method actionPerformed is called from the EDT so what happens is that the sleep call just stops the EDT itself: you can imagine that this is not how it should work, no EDT running means no GUI updates.

Since it's a time consuming task you should implement it in a Thread/Runnable so that you can execute it in parallel and then call the invokeLater from this other thread.

Something like:

class LongProcess extends Thread {
  public void run() {
    for (int i = 0; i < 10; ++i) {
      SwingUtilities.invokeLater(...);
      Thread.sleep(300);
    }
  }
}

void actionPerformed(ActionEvent e) {
  LongProcess process = new LongProcess();
  process.start();
}
Jack
  • 131,802
  • 30
  • 241
  • 343
  • Jack beat me to the answer while I was typing it. So I'll just +1 his because it's exactly what I would say :) Just a side note to add that `SwingUtilities.invokeLater()` should be used in threads to update Swing/AWT components. – george_h Jun 13 '12 at 04:36
  • Thank you @Jack and george_h! Much appreciated! I didn't understand that the actionPerformed came from the EDT, but I guess it makes sense now. Thanks for the quick response! – Matthew Jun 13 '12 at 04:38