views:

138

answers:

1

Hi there. I'm wondering if SwingWorker has to be a nested class within my main GUI. I'd rather make it an external class to keep the GUI clear from any of my programs logic.

I tried to make the SwingWorker class external, which works fine for the process, unfortunately I can't access any of my GUI fields from the SwingWorker class. Whenever I try to access an attribute, such like a label or whatever from within SwingWorker's done() method I get a nullPointer exception.

Any advice would be much appreciated!


First of all thank you very much Jeff! Works fine so far, even though I could not follow you on the second option you presented. One of my background tasks calculates a certain size (long value), so it would be nice to get that value from my GUI.

You suggested to work with getters and setters but unfortunately I've got no idea on how to implement them in the SwingWorker class.

I tried it like this:

public void setSize(long totalSize) {
     this.totalSize = totalSize;
}
public long getTotalSize() {
     return totalSize;
}

The setter is invoked at the end of the doInBackground() method. Unfortunately I can't use the get() method from my GUI.

final MySwingWorker w = new MySwingWorker();
Runnable r = new Runnable() {
    public void run() {
        // do something with w.get()
    }
};
w.setRunnable(r);
w.execute();

The object creation of "w" does not work in my case as the constructor requires an object of Runnable. Am I missing something? Please go easy on me, it's the first time I work with SwingWorker. :) Again, thank you very much for your help!

+3  A: 

You can make the SwingWorker an external class. However, just like any other class, if it can't see the variables (e.g. the label you want to set), of course it won't be able to set it. One thing you could do is pass the worker a Runnable that it executes when it is complete.

public class MySwingWorker extends SwingWorker {

   private final Runnable r;
   public MySwingWorker(Runnable r) {
       this.r = r;
   }
   public void doInBackground() {...}
   public void done() { r.run(); }
 }

Now from the GUI, you might do something like

Runnable updateLabel = new Runnable() {
       public void run() {
           label.setText("myValue");
       }
};
SwingWorker w = new MySwingWorker(updateLabel);
w.execute();

This gets a bit trickier if you want to use the result of the SwingWorker, though it is possible. Rather than passing the Runnable to the swing worker's constructor, you would have a setter method and then it would be something like:

final MySwingWorker w = new MySwingWorker();
Runnable r = new Runnable() {
    public void run() {
        // do something with w.get()
    }
};
w.setRunnable(r);
w.execute();

In either case, the Runnable is functioning similarly to a closure that is executed when the worker is finished.

Jeff Storey
It would also be possible to simply pass the Label in the constructor rather than the runnable. Defining a new worker subclass for each little action would probably be overkill though. Anonymous classes are great for solving this particular problem of putting together a very small, tightly coupled object.
TK
It really depends if this is a single use case. The abstraction works if you're doing it in many places. Oh how closures would be helpful.
Jeff Storey