views:

212

answers:

2

My "problem" can be described by the following. Assume we have an intensive process that we want to have running in the background and have it update a Swing JProgress bar. The solution is easy:

import java.util.List;

import javax.swing.JOptionPane;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;


/**
 * @author Savvas Dalkitsis
 */
public class Test {

    public static void main(String[] args) {
        final JProgressBar progressBar = new JProgressBar(0,99);
        SwingWorker<Void, Integer> w = new SwingWorker<Void, Integer>(){

            @Override
            protected void process(List<Integer> chunks) {
                progressBar.setValue(chunks.get(chunks.size()-1));
            }

            @Override
            protected Void doInBackground() throws Exception {

                for (int i=0;i<100;i++) {
                    publish(i);
                    Thread.sleep(300);
                }

                return null;
            }

        };
        w.execute();
        JOptionPane.showOptionDialog(null,
                new Object[] { "Process", progressBar }, "Process",
                JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE,
                null, null, null);
    }

}

Now assume that i have various methods that take a long time. For instance we have a method that downloads a file from a server. Or another that uploads to a server. Or anything really. What is the proper way of delegating the publish method to those methods so that they can update the GUI appropriately?

What i have found so far is this (assume that the method "aMethod" resides in some other package for instance):

import java.awt.event.ActionEvent;
import java.util.List;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JOptionPane;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;


/**
 * @author Savvas Dalkitsis
 */
public class Test {

    public static void main(String[] args) {
        final JProgressBar progressBar = new JProgressBar(0,99);
        SwingWorker<Void, Integer> w = new SwingWorker<Void, Integer>(){

            @Override
            protected void process(List<Integer> chunks) {
                progressBar.setValue(chunks.get(chunks.size()-1));
            }

            @SuppressWarnings("serial")
            @Override
            protected Void doInBackground() throws Exception {

                aMethod(new AbstractAction() {

                    @Override
                    public void actionPerformed(ActionEvent e) {
                        publish((Integer)getValue("progress"));
                    }
                });

                return null;
            }

        };
        w.execute();
        JOptionPane.showOptionDialog(null,
                new Object[] { "Process", progressBar }, "Process",
                JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE,
                null, null, null);
    }

    public static void aMethod (Action action) {
        for (int i=0;i<100;i++) {
            action.putValue("progress", i);
            action.actionPerformed(null);
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

It works but i know it lacks something. Any thoughts?

A: 

Perhaps do a SwingWorker for each long method. Each SwingWorker has it's own progress level.

Each SwingWorker update it's own progress level during doInBackground method, then call publish. Inside the process method, so inside the EDT, each SwingWorker read it's progress level, and update model and vision general progress bar.

Istao
what if we have just one terrible long method? I am not downvoting, but this is not a solution
Xorty
You can downvoting if my answer is bad. But I read in the question "For instance we have a method that downloads a file from a server. Or another that uploads to a server. Or anything really."... so I suppose there's a lot of methods, not just one terrible long ?
Istao
The problem i have is that these methods can be called from different parts of the code. And the gui element they will need to refresh will not always be the same. With my solution i can create a generic method and each time i create a new swingworker i can assing the process method on my gui.
Savvas Dalkitsis
A: 

I faced similar problem. Here is what I found, maybe the really correct answer lacks, but let's give it a try :

  • if we have lot of iterations, we can update progress bar in doInBackGround() method. JProgressBar is constructor parameter of our SwingWorker which extends SwingWorker (so yes, we use custom)
  • if we do not have iterations and we can not interrupt method which takes lot of time to complete, we can either screw the whole thing and do it as most people (so our progress bar has not linear process but it just refreshes it values after partial job is done). Bad news is, if our method is the only which worker does (f.e. sending e-mail on background) progress bar will become suddenly full. Not very nice though, lets take a look on third option
  • this might be quite crazy and performance slowing, it's all because our app must be fancy. So let's get to the source code of method which takes long time to complete. We override it and paste exactly same code, but we add one more parameter - guess what, yes JProgressBar. Inside of method we create Thread which will run until some boolean parameter (flag indicating method is finally completed) is set on true. Thread will keep updating JProgressBar in some reasonable intervals. The biggest problem is to assume, what is the reasonable interval. We should take some tests and estimate value for intervals.

In third point I described how to execute Thread from method which completes some task which is not iterative (at least not in our Java code) and can not be interrupted. Thread updates JProgressBar which was given as a method parameter. This is , however, definitelly slower as pure method calling

Xorty