0

I have code that redirects system output to a jtext area, but that jtextarea doesnt update until the code is finished running. How do I modify the code to make the jtextarea update in real time during runtime?

private void updateTextArea(final String text) {
        SwingUtilities.invokeLater(new Runnable() {
          public void run() {
            consoleTextAreaInner.append(text);
          }
        });
      }
private void redirectSystemStreams() {
      OutputStream out = new OutputStream() {
        @Override
        public void write(int b) throws IOException {
          updateTextArea(String.valueOf((char) b));
        }

        @Override
        public void write(byte[] b, int off, int len) throws IOException {
          updateTextArea(new String(b, off, len));
        }

        @Override
        public void write(byte[] b) throws IOException {
          write(b, 0, b.length);
        }
      };

      System.setOut(new PrintStream(out, true));
      System.setErr(new PrintStream(out, true));
    }    

The rest of the code is mainly just an actionlistener for a button:

private void updateButtonActionPerformed(java.awt.event.ActionEvent evt) {                                             
    // TODO add your handling code here:
    String shopRoot = this.shopRootDirTxtField.getText();
    String updZipPath = this.updateZipTextField.getText();
    this.mainUpdater = new ShopUpdater(new File(shopRoot), updZipPath);
    this.mainUpdater.update();
}

That update() method begins the process of copying+pasting files on the file system and during that process uses system.out.println to provide an up-to-date status on where the program is currently at in reference to how many more files it has to copy.

Giardino
  • 1,367
  • 3
  • 10
  • 30

2 Answers2

3

It's hard to tell with 100% assuredly what is going wrong, as there's a lot of critical involved code that we're not seeing, but there's a good chance that you're running into a Swing threading issue. You need to read the stream in a background thread and then call your update to text area method on the Swing event thread, the EDT. Otherwise you'll tie up the EDT while waiting for the stream to read, completely freezing your GUI. A SwingWorker would work well. Consider publishing to the JTextArea each time a new line is encountered.

For example:

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;

import javax.swing.JTextArea;
import javax.swing.SwingUtilities;

public class MyRedirectStream {

   private JTextArea textArea;

   public MyRedirectStream(JTextArea gui) {
      this.textArea = gui;
   }

   public void redirect() {
      System.setOut(new PrintStream(new MyOutStream(), true));
      System.setErr(new PrintStream(new MyOutStream(), true));      
   }

   private class MyOutStream extends OutputStream {
      private static final int MAX_LENGTH = 1600;
      private StringBuilder sb = new StringBuilder(MAX_LENGTH + 100);

      @Override
      public void write(int b) throws IOException {
         sb.append((char)b);
         if (sb.length() > MAX_LENGTH) {
            sendToTextArea(sb.toString());
            sb = new StringBuilder();
         }
      }

      @Override
      public void flush() throws IOException {
         sendToTextArea(sb.toString());
         sb = new StringBuilder();
         super.flush();
      }
      private void sendToTextArea(final String text) {
         SwingUtilities.invokeLater(new Runnable() {
            public void run() {
               textArea.append(text);
            }
         });
      }

   }   
}

and:

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class StreamToTextArea2 {
   public static void main(String[] args) {
      final TextPanel gui = new TextPanel();
      final MyRedirectStream myRedirectStream = new MyRedirectStream(
            gui.getTextarea());
      myRedirectStream.redirect();

      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            final JFrame frame = new JFrame("Redirect");
            JButton showTextBtn = new JButton(new AbstractAction("Show Text") {

               @Override
               public void actionPerformed(ActionEvent arg0) {
                  JFileChooser fileChooser = new JFileChooser();
                  int result = fileChooser.showOpenDialog(frame);
                  if (result == JFileChooser.APPROVE_OPTION) {
                     gui.clearText();
                     final File file = fileChooser.getSelectedFile();
                     new Thread(new Runnable() {
                        public void run() {
                           try {
                              Scanner scan = new Scanner(file);
                              while (scan.hasNextLine()) {
                                 System.out.println(scan.nextLine());
                              }
                           } catch (FileNotFoundException e) {
                              e.printStackTrace();
                           }

                        }
                     }).start();
                  }
               }
            });
            JButton clearTextBtn = new JButton(
                  new AbstractAction("Clear Text") {

                     @Override
                     public void actionPerformed(ActionEvent e) {
                        gui.clearText();
                     }
                  });
            JPanel btnPanel = new JPanel();
            btnPanel.add(clearTextBtn);
            btnPanel.add(showTextBtn);

            frame.getContentPane().add(gui);
            frame.getContentPane().add(btnPanel, BorderLayout.SOUTH);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);

         }
      });

   }
}

import java.awt.BorderLayout;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

class TextPanel extends JPanel {
   private JTextArea textarea = new JTextArea(20, 40);

   public TextPanel() {
      setLayout(new BorderLayout());
      JScrollPane scrollPane = new JScrollPane(textarea);
      scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
      add(scrollPane);
   }

   public JTextArea getTextarea() {
      return textarea;
   }

   public void clearText() {
      textarea.setText("");
   }

}
Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • +1, curious about the new Runnable(), do you know if it will be cached or is a thread spawned on every read of a byte? – arynaq Oct 31 '13 at 22:31
  • @arynaq: no it will not be cached. This code is not very efficient. Better to probably build a buffer or StringBuilder and then batch process data when the buffer is big enough. – Hovercraft Full Of Eels Oct 31 '13 at 22:53
  • @arynaq: now it will be cached some. – Hovercraft Full Of Eels Nov 05 '13 at 18:16
  • @HovercraftFullOfEels I can't seem to import `TextPanel` and subsequently `.getTextArea()` and `.clearText()` Is TextPanel a mistake or should I be seeing that as part of one of the J packages? In all your code, those are the only showing as unidentifiable. I have all the JGoodies libraries installed and am using IntelliJ Idea as my iDE. Thanks in advance. – dbconfession Feb 22 '15 at 07:04
  • @dbconfession: sorry, left out. – Hovercraft Full Of Eels Feb 24 '15 at 14:57
  • @dbconfession: I added the missing class that you were asking about, TextPanel. – Hovercraft Full Of Eels Feb 24 '15 at 22:11
  • @HovercraftFullOfEels I don't think your code address the OP's question. It's a good example of his problem though. He's asking how to make console information append to JTextArea in real time. Your code takes the context of a text file and spits everything out all at once to jTextArea. If I understand correctly, just as the console displays information as it is happening, so too should the JTextArea display in real time, instead of just dumping the final result of the consoles output all at once. – dbconfession Feb 24 '15 at 22:45
1

You did write invokeLater, and it does invoke it later. How much late is later is indeterminate unless you perform something active to make it determinate. You could, for example, insert Thread.sleep calls between files.

Jonathan Rosenne
  • 2,159
  • 17
  • 27