0

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();
    }
}
Community
  • 1
  • 1
austinthemassive
  • 5,907
  • 6
  • 21
  • 25
  • 2
    You should not call any methods on any swing objects, nor construct any swing objects, from inside the `doInBackground` method, nor call any methods which do so directly. Your `doInBackground` calls to `.add`, `.repaint`, `new JLabel`, all violate this rule. Modify the gui only from inside SwingWorker's `process` or `done`. – Mike Clark Nov 05 '13 at 19:35

3 Answers3

6

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.

dic19
  • 17,821
  • 6
  • 40
  • 69
  • This makes sense. I was thinking that publish was the key somehow, and I was also wondering about using Swing components in SwingWorker. One question I still have, though, is will i need to call this.proccess() in .doInBackground()? – austinthemassive Nov 06 '13 at 17:25
  • 1
    Hi there! No, actually you never call `process()` method explicitly. Every call to `publish()` method puts a value (chunk) in a queue that will be processed asynchronously by the `SwingWorker` itself through `process()` method. This way the `SwingWorker` ensures `process()` method runs in the EDT. So you just have to define what `process()` method must do but you never call it explicitly. @austinthemassive – dic19 Nov 06 '13 at 18:06
  • Ok, that makes sense. Since you say process() is in the EDT, could I just add the label to the panel there? – austinthemassive Nov 07 '13 at 16:06
  • Yes you can and it's ok. Don´t forget to call `panel.revalidate()` after adding the label. @austinthemassive – dic19 Nov 07 '13 at 18:03
  • 1
    I did, and it now works! Thanks for following up. I was going to update this thread when I had some extra time. In short, my understanding of Swing Worker was off, but now I'm good to go. – austinthemassive Dec 18 '13 at 04:23
  • 1
    this answer makes me happiest person in the world,gratz. – Vivian Maya Jan 06 '15 at 11:46
0

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.

markusw
  • 1,975
  • 16
  • 28
0

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

Community
  • 1
  • 1
alex2410
  • 10,904
  • 3
  • 25
  • 41