1

Here is another one of those 'how do I get a JTextArea to update in real time' questions. I've read every one of the existing posts, and I think that I am threading this correctly. However, I'm still having that same problem - my JTextArea doesn't update until the loop has finished and then it updats in one big blast. Can somebody see where I am doing this incorrectly? I can't see it. Thanks!

EDIT: switch the text area updating the called method. But I still have the same result!

private void saveImagesToDisk() {

    textArea.append("\nPreparing to save photos to photos directory\n\n");

    for (MediaFeedData mfd : imagesMetaDataList) {
        try {
            String imageURL = mfd.getImages().getLowResolution().getImageUrl();
            final String filename = mfd.getId(); // just name the file using the image id from instagram
            System.out.println(filename);

            SaveImageFromUrl.saveImage(imageURL, filename, textArea);

        } catch (IOException e) {
            e.printStackTrace();

        }
    }

}

Then, on in the save method, I have this:

public class SaveImageFromUrl {

public static Boolean saveImage(final String imageUrl, String destinationFile, final JTextArea textArea) throws IOException {

    String directoryName = "photos";
    if (!makePhotosDirectory(directoryName, textArea)) {return false;}

    File file = new File(directoryName, destinationFile);
    URL url = new URL(imageUrl);
    InputStream is = url.openStream();
    OutputStream os = new FileOutputStream(file);

    byte[] b = new byte[2048];
    int length;

    while ((length = is.read(b)) != -1) {
        os.write(b, 0, length);
    }

    is.close();
    os.close();

    new Thread() {
        public void run() {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    textArea.append(imageUrl + " written to photos directory\n");
                }
            });
        }       
    }.start();


    return true;
}
AndroidDev
  • 20,466
  • 42
  • 148
  • 239
  • 1
    Your threading is a mess. Why are you creating a new thread to use `SwingUtilities.invokeAndWait`? What part of this code takes a long time to process, the `saveImage` call? – thatidiotguy Nov 07 '14 at 16:59
  • None of it is particularly slow. I've also tried `invokeLater` (that seems to run a little faster), but even if I remove all of the stuff about the Swing output, the loop runs acceptably fast. Speed isn't the issue - getting the text area to update with each iteration is. Especially if I am downloading hundreds of images. – AndroidDev Nov 07 '14 at 17:04
  • Yes, it's both use of the for loop and the saveImage method that are tying up the Swing event thread and causing your GUI not to work. Please see information in answer below for more. – Hovercraft Full Of Eels Nov 07 '14 at 17:28
  • Thanks, Hovercraft. I've also tried to wrap that invokeLater in a new thread. Why doesn't this solve the problem? – AndroidDev Nov 07 '14 at 17:45

1 Answers1

2

Both the for loop and the code in the method below need to be called in a background thread, and this you're not doing.

if (SaveImageFromUrl.saveImage(imageURL, filename, textArea)) {

Just using a background thread will not help if the time-intensive code is not the code that is called in the background.

Consider using a SwingWorker, something like,...

   private void saveImagesToDisk() {
      textArea.append("\nPreparing to save photos to photos directory\n\n");

      final SwingWorker<Void, String> imageWorker = new SwingWorker<Void, String>() {
         @Override
         protected Void doInBackground() throws Exception {

            for (MediaFeedData mfd : imagesMetaDataList) {
               final String imageURL = mfd.getImages().getLowResolution().getImageUrl();
               final String filename = mfd.getId();
               System.out.println(filename);
               String textToPublish = "";
               if (SaveImageFromUrl.saveImage(imageURL, filename, textArea)) {
                  textToPublish = filename + " written to photos directory\n";
               } else {
                  textToPublish = filename + " not saved!\n";
               }

               // publish String so it can be used in the process method
               publish(textToPublish);
            }
            return null;
         }

         @Override
         protected void process(List<String> chunks) {
            // Strings sent to the EDT by the publish method
            // This called on the Swing event thread.
            for (String chunk : chunks) {
               textArea.append(chunk);
            }
         }
      };

      imageWorker.addPropertyChangeListener(new PropertyChangeListener() {

         @Override
         public void propertyChange(PropertyChangeEvent evt) {
            if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
               try {
                  // need to call this on the event thread
                  // to catch any exceptions that have occurred int he Swing Worker
                  imageWorker.get();
               } catch (InterruptedException e) {
                  e.printStackTrace();
               } catch (ExecutionException e) {
                  // TODO catch exceptions buried in this guy
                  e.printStackTrace();
               }
            }
         }
      });

      // run our SwingWorker
      imageWorker.execute();
   }

For more details, please read Concurrency in Swing.

Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • Thank you very much for this. It works, but the problem that I have now is it runs about 10x slower. Is this normal? – AndroidDev Nov 07 '14 at 17:46
  • 1
    @usr55410: start with correct and then [profile](http://stackoverflow.com/q/2064427/230513) for fast; +1 for `SWingWorker`. – trashgod Nov 07 '14 at 17:56