Search code examples
javaswingconsoleswingworker

System.setOut with PipedOutputStream


I have develop a small console using JTextArea. I read some tutorials and know something. But still I have a problem. This is my code.

public class Console extends JFrame {

    public Console() throws IOException {
        setSize(492, 325);
        setLayout(new BorderLayout());
        setDefaultCloseOperation(3);
        setVisible(true);

        final JTextArea area = new JTextArea();
        add(area, BorderLayout.CENTER);

        JButton button = new JButton("test");
        add(button, BorderLayout.EAST);
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("Test with Button Click.."); // this is not print in textarea.
            }
        });

        final PipedInputStream pis = new PipedInputStream();
        PipedOutputStream pos = new PipedOutputStream(pis);
        System.out.println("Test Before setOut.");
        System.setOut(new PrintStream(pos, true));

        System.out.println("Test After setOut."); // this is printed in my textarea.
        new SwingWorker<Void, String>() {
            @Override
            protected Void doInBackground() throws Exception {
                Scanner scan = new Scanner(pis);
                while (scan.hasNextLine())
                    area.append(scan.nextLine());
                return null;
            }

        }.execute();

    }

    public static void main(String[] args) throws Exception {
        new Console();
    }
}

This is my output..
Output

When the button is clicked, System.out.println not work with textarea. I don't know what i'm does wrongly.


Solution

  • When you call SwingWorker.execute(), it only runs once. So here, it reads the first println(), reaches the end of the document, and stops running.

    A better solution would be to implement your own OutputStream and have its write() method append to the text area, using SwingUtilities.invokeLater() to make sure this is done on the AWT thread.

    Something roughly like this:

    class TextAreaOut extends OutputStream implements Runnable {
        JTextArea text;
        String buffer = "";
    
        TextAreaOut(JTextArea text) {
            this.text = text;
            System.setOut(new PrintStream(this));
        }
    
        @Override
        public synchronized void run() {
            text.append(buffer);
            buffer = "";
        }
    
        @Override
        public synchronized void write(int b) throws IOException {
            if(buffer.length() == 0) {
                SwingUtilities.invokeLater(this);
            }
            buffer += (char)b;
        }
    }