Question

I originally was attempting to update a JFrame and JPanel several times while in a Java Action Listener, but both would only update when the Action Listener completed all its tasks. Here is the link to my original question (Refreshing a JFrame while in an Action Listener).

I was told in the feedback to that question that Swing Worker should solve my problems. However, when I implemented Swing Worker (as seen below), nothing changed. The JFrame and JPanel still updated only when the Action Listener completed all tasks. My question is, am I missing something below? If not, how can I implement this in an Action Listener to properly update the Frame and Panel timely?

@Override
protected Integer doInBackground() throws Exception{
    //Downloads and unzips the first video.  
    if(cameraBoolean==true)
        panel.add(this.downloadRecording(camera, recording));
    else
        panel.add(new JLabel("Could not contact camera "+camera.getName()));

    panel.repaint();
    jframe.repaint();
    return 1;
}

private JLabel downloadRecording(Camera camera, Recording recording){
    //does a bunch of calculations and returns a jLabel, and works correctly
}

protected void done(){
    try{
        Date currentTime = new Timestamp(Calendar.getInstance().getTime().getTime());
        JOptionPane.showMessageDialog(jframe, "Camera "+camera.getName()+" finished downloading at "+currentTime.getTime());
    }catch (Exception e){
    e.printStackTrace();
    }
}
Was it helpful?

Solution

You have a misundertanding about how SwingWorker works. This class is intended to provide a way to update the GUI while heavy tasks are being performed. All of this is because Swing components updates take place in the Event Dispatch Thread (a.k.a. EDT) which is a particular thread.

For instance, if you click a button and perform a time consuming task all in the EDT, then this thread will block untill this task finishes. Consequently, you'll see your GUI is frozen.

Keeping this in mind, doInBackground() method runs in another different thread that's not the EDT which is ok. So don't call any Swing method in there:

protected Integer doInBackground() throws Exception{
    //Downloads and unzips the first video.  
    if(cameraBoolean==true) // just use if(cameraBoolean), since this is a boolean
        panel.add(this.downloadRecording(camera, recording)); // NO!
    else
        panel.add(new JLabel("Could not contact camera "+camera.getName())); //NO!

    panel.repaint(); //NO, never!
    jframe.repaint();//NO, never!
    return 1;
}

Add a JLabel to this panel before executing your SwingWorker and update its text using publish() and process() methods instead:

JPanel panel = new JPanel();
final JLabel progressLabel = new JLabel("Some text before executing SwingWorker");
panel.add(progressLabel);

SwingWorker<Integer, String> worker = new SwingWorker<Integer, String>() {    
    @Override
    protected Integer doInBackground() throws Exception {
        if(cameraBoolean){
            pubish("Starting long process...");
            //Some processing here
            publish("Intermediate result to be published #1");
            //Some other processing stuff
            publish("Intermediate result to be published #2");
            //And so on...
            return 0;
        } else {
            publish("Could not contact camera "+camera.getName());
            return -1;
        }
    }

    @Override
    protected void process(List<String> chunks) {
        for(String string : chunks){
            progressLabel.setText(string);
        }
    }

    @Override
    protected void done() {
        progressLabel.setText("Finished!!!");
    }
};

worker.execute();

Both process() and done() methods take place in the EDT so it's safe make GUI updates there. Take a look to this excelent example: Swing Worker Example for more details.

OTHER TIPS

Maybe because you repaint your panel/frame just when the synchronous call this.downloadRecording(camera, recording) is finished?

Try to only put this call into the doInBackground() method, because (so I guess) that's the one that takes a long time and for all this time the JFrame gets not refreshed.

You can't update UI in next way:

 panel.repaint();
 jframe.repaint();

In your doInBackground method you must to call publish(V... chunks) method, that Sends data chunks to the process(java.util.List<V>) method.(according docs) and than in method process(List<V> chunks) you can update your UI(according docs process method - Receives data chunks from the publish method asynchronously on the Event Dispatch Thread.). SwingWorker docs.

So, override process method for updating, and call publish method.

Also you can use Executors for background processes. In this case your UI will be working in EDT and your background process in another thread. Example:

Executors.newSingleThreadExecutor().execute(new Runnable() {

        @Override
        public void run() {
            // run background process

        }
    });

EDIT: good example of SwingWorker

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