tags:

views:

72

answers:

5

I have this code:

try {
    SwingUtilities.invokeAndWait(new Runnable() {
        public void run() {
        try {
            dialog.handleDownload();
        } catch (IOException io) {
            io.printStackTrace();
            }
        }
    });
} catch(Exception io) { io.printStackTrace(); }

in the handleDownload I'm reading an inputstream, calculating a progress bar's value, and setting it to that. So, when I click a button, a new JFrame opens up and does all the stuff I wrote above.

If I have the dialog.handleDownload by itself ( in no SwingUtilities method ), it freezes until the operation is finished. If I add it in a invokeLater it's closed very fast ( I can't see anything, and the operation is not finished ). If I add it in a invokeAndWait I get the invokeAndWait cannot be called from the event dispatcher thread error. What should I do?

+4  A: 

If you are doing that in response to a button click, you are already in the event thread so invokeAndWait is actually going in the wrong direction.

You need to start a new thread to execute the handleDownload thread that is NOT a the event dispatch thread--BUT

When running in your new thread, be sure that any GUI updates use invokeAndWait or preferably invokeLater to get back to the EDT.

The simple rules to remember:

  • Any thread handed to you by Swing is the EDT, so do all the GUI stuff on it you want
  • Do ALL updates of GUI elements on the EDT (ONLY).
  • Do anything that takes a long time on a non-EDT thread (Start a new thread).
  • Use invokeLater to get back to the EDT from a non-EDT thread
Bill K
+1  A: 

You shouldn't be accessing your inputStream in the event thread. Spawn a new thread which actually does the bulk of the handleDownload() work, then make the last operation performed by that thread be to call SwingUtilities.invokeLater() with the code that actually shows and populates the dialog.

Curtis
+1  A: 

What does "handleDownload" do? Time consuming things should not be done in the event dispatcher thread. If something is consuming lots of CPU cycles in the event dispatcher thread, then the display will freeze until it's done. You are far better off in a case like that invoking a normal thread (not using SwingUtilities) to do the processing outside of the event dispatcher thread, and in that thread using SwingUtilities.invokeLater to send back notifications that things have changed (like updating a progress bar) at regular intervals.

Paul Tomblin
+3  A: 

It looks like you could make use of SwingWorker. This allows you to defer an expensive operation to a background thread (keeping your GUI responsive) and when the operation is finished, do some stuff to the GUI.

Edit: Example

Here's a bit more complex example that shows how to use the basics of SwingWorker but also how to publish/process intermediate results.

public static void main(String[] args) {
    final int SIZE = 1024*1024; //1 MiB

    //simulates downloading a 1 MiB file
    final InputStream in = new InputStream() {
        int read = 0;
        public int read() throws IOException {
            if ( read == SIZE ) {
                return -1;
            } else {
                if ( read % 200 == 0 ) {
                    try { Thread.sleep(1); } catch ( InterruptedException e ) {}
                }
                read++;
                return 5;
            }
        }
    };

    final JProgressBar progress = new JProgressBar(0, SIZE);

    final JButton button = new JButton("Start");
    button.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            button.setText("Working...");
            SwingWorker<byte[], Integer> worker = new SwingWorker<byte[], Integer>() {
                @Override
                protected byte[] doInBackground() throws Exception {
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    byte[] buff = new byte[1024];
                    for ( int read = -1; (read = in.read(buff)) != -1; ) {
                        baos.write(buff, 0, read);
                        publish(read);
                    }
                    return baos.toByteArray();
                }

                @Override
                protected void process(List<Integer> chunks) {
                    int total = 0;
                    for ( Integer amtRead : chunks ) {
                        total += amtRead;
                    }
                    progress.setValue(progress.getValue() + total);
                }

                @Override
                protected void done() {
                    try {
                        byte[] data = get();
                        button.setText("Read " + data.length + " bytes");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            };
            worker.execute();
        }
    });

    JFrame frame = new JFrame();
    frame.setLayout(new BorderLayout());
    frame.add(button, BorderLayout.NORTH);
    frame.add(progress, BorderLayout.SOUTH);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.pack(); frame.setVisible(true);
}

Edit: Changed the example to drive a progress bar as if a download were taking place.

Mark Peters
Looks like you beat me to it with an example! Thanks.
Mark Peters
@Mark...I accidentally added the code to your answer instead of mine....oops.
jjnguy
@Mark, if you have a code example, you should post it. Sorry...
jjnguy
@jjnguy: Lol alright, added the example in. Don't worry about it.
Mark Peters
That's a really good example. I would change the loop to be this though: `for ( int i = 0; i < 100; i++ ) { num += 5; publish(num); Thread.sleep(100); }`
jjnguy
@jjnguy: With that example I actually wanted to demonstrate that the number of times publish is called does not dictate how many times process is called (i.e., the intermediate results are automatically collated for you). Adding a sleep would change that to a 1:1 ratio. Was there a specific reason for suggesting it that I'm missing?
Mark Peters
@Mark, on my machine that number just flies up and seems nonsensical to me. Making it move a bit slower helps me to see what is actually happening.
jjnguy
@jjnguy: Fair enough. I mocked an input stream and now drive a progress bar, which has similarities to handling a download now.
Mark Peters
@Mark, that looks great.
jjnguy
+1  A: 

What it sounds like you need is a SwingWorker. This will allow you to have the file download take place in a separate thread that doesn't bother the EDT.

Your code would look something like this:

class Downloader extends SwingWorker<String, Void> {
   @Override
   public String doInBackground() {
       dialog.handleDownload();
       return "done";
   }

   @Override
   protected void done() {
       try { 
           someLabel.setText(get());
       } catch (Exception ignore) {
       }
   }
}
jjnguy