简体   繁体   中英

Redirecting SOP console messages to JTextArea and also should not flicker and should print line by line same as java console

My application is consisting of a program which is a long running one which involves servlet call, file import and stuffs. I have added a Swing UI which calls this program through "run" button and in Swing UI there is a JTextArea component which would print all the System.out.println messages instead of the standard console. Requirement is also that it should be real time and should print line by line same as actual console.

I am able to redirect those messages to JtextArea with new class which will override the OutputStream and would call this redirection method once. Code is as below:

In Utils Class added below method

public static void redirectConsoleMsgs(JTextArea jTextArea) {
    try {
        //Console output
        PrintStream printStream =
            new PrintStream(new CustomOutputStream(jTextArea));

        // keeps reference of standard output stream
        PrintStream standardOut = System.out;
        PrintStream standardErr = System.err;

        // re-assigns standard output stream and error output stream
        System.setOut(printStream);
        System.setErr(printStream);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Calling the redirection method once during the initialization loading of GUI in jbinit() method as below:

    //Redirecting Output msg to console.
    Utils.redirectConsoleMsgs(jTextArea1);

In Java Frame class, button push code which triggers the program(contains SOP messages) is as below:

    private void jButton6_actionPerformed(ActionEvent e) {

    //doProcess1();
    System.out.println("-------------------------------------"+SwingUtilities.isEventDispatchThread());  //returning true
    MyProcess prc = new MyProcess();
    prc.doProcessing();  

}

So far these are the achieved scenarios:

  1. All the messages are redirecting to the jTextArea1.
  2. textArea.update(textArea.getGraphics()); with this line printing in jTextArea1 is happening but along with flickering.
  3. when removed the second point code line. GUI freezes and the messages are all printed at once at the end when the program processing ends.

I read lots of blogs and tried SwingUtilites and SwingWorkers but still not getting the desired result, still stuck with the 3 point issue. I have already read all the stackoverflow questions related to same issues, but no luck.

Somewhere I messed up with the EDT and multi threading in swings GUI and button click (Event Dispatch thread)

please advise.

thank you.

EDIT 1:

Below is a sample project which I am adding as per the request of @Hovercraft full of eels

these are four classes as below:

1. CustomOutputStream.java

package com.client;

import java.io.IOException;

import java.io.OutputStream;

import javax.swing.JTextArea;

public class CustomOutputStream extends OutputStream {
    private JTextArea textArea;
    private int varb;

    public CustomOutputStream(JTextArea textArea) {
        this.textArea = textArea;
    }

    @Override
    public void write(int b) throws IOException {
        /// redirects data to the text area
        textArea.append(String.valueOf((char)b));
        // scrolls the text area to the end of data
        textArea.setCaretPosition(textArea.getDocument().getLength());
        //textArea.getCaret().setBlinkRate(0);
        // keeps the textArea up to date
        textArea.update(textArea.getGraphics());

    }

    @Override
    public void write(byte[] b, int off, int len) throws IOException {

        /// redirects data to the text area
        textArea.append(new String(b, off, len));
        // scrolls the text area to the end of data
        textArea.setCaretPosition(textArea.getDocument().getLength());
        //textArea.getCaret().setBlinkRate(0);
        // keeps the textArea up to date
        textArea.update(textArea.getGraphics());
    }
}

2. Main.java

 package com.client;

    import java.io.PrintStream;

    import javax.swing.JTextArea;

    public class Main {
        public void doProcessing(){
            for(int i=0;i<=1000;i++){
                System.out.println("Printing Line............... this is line # "+i);
            }
        }

        public static void redirectConsoleMsgs(JTextArea jTextArea) {
            try {
                //Console output
                PrintStream printStream =
                    new PrintStream(new CustomOutputStream(jTextArea));

                // keeps reference of standard output stream
                PrintStream standardOut = System.out;
                PrintStream standardErr = System.err;

                // re-assigns standard output stream and error output stream
                System.setOut(printStream);
                System.setErr(printStream);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

3. ConsoleFrame.java

package com.client;

import java.awt.Dimension;

import java.awt.Rectangle;
import java.awt.TextArea;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import java.io.PrintStream;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

public class ConsoleFrame extends JFrame {
    private TextArea textArea1 = new TextArea();
    private JTextArea jTextArea1 = new JTextArea();
    private JButton jButton1 = new JButton();
    private JScrollPane scroll = new JScrollPane(jTextArea1);

    public ConsoleFrame() {
        try {
            jbInit();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void jbInit() throws Exception {
        //redirecting console messages.
        Main.redirectConsoleMsgs(jTextArea1);
        this.getContentPane().setLayout( null );
        this.setSize(new Dimension(694, 534));
        this.setTitle( "Console" );
        jTextArea1.setBounds(new Rectangle(20, 10, 640, 415));
        scroll.setBounds(new Rectangle(20, 10, 640, 415));
        jButton1.setText("run");
        jButton1.setBounds(new Rectangle(235, 445, 75, 21));
        jButton1.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    jButton1_actionPerformed(e);
                }
            });
        this.getContentPane().add(jButton1, null);
        //this.getContentPane().add(jTextArea1, null);
        this.getContentPane().add(scroll, null);

    }

    private void jButton1_actionPerformed(ActionEvent e) {
            Main main = new Main();
            main.doProcessing();
    }  
}

4. ConsoleMain.java

package com.client;

import java.awt.Dimension;
import java.awt.Toolkit;

import javax.swing.JFrame;
import javax.swing.UIManager;

public class ConsoleMain {
    public ConsoleMain() {
        JFrame frame = new ConsoleFrame();
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        Dimension frameSize = frame.getSize();
        if (frameSize.height > screenSize.height) {
            frameSize.height = screenSize.height;
        }
        if (frameSize.width > screenSize.width) {
            frameSize.width = screenSize.width;
        }
        frame.setLocation( ( screenSize.width - frameSize.width ) / 2, ( screenSize.height - frameSize.height ) / 2 );
        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception e) {
            e.printStackTrace();
        }
        new ConsoleMain();
    }
}

You need to place all files as per the package structure then have to run the ConsoleMain.java which will invoke the ConsolFrame.java. Once you run you would be able to see a Swing UI with text Area and Run button. Click the Run button and you will be able to see the issues.

Expectation is to see it working same as standard java console output, smooth and thread safe.

textArea.update(textArea.getGraphics());

Don't invoke update(...) directly. Swing will invoke that method when it is appropriate.

And don't use getGraphics(). Again, Swing will invoke the appropriate painting methods as required and provide the appropriate Graphics object.

By invoking the append(...) method, the text area is smart enough to repaint itself.

You can also check out Message Console for a working example of redirecting output.

Edit:

it is more related to multi threading

That is correct. You should NOT be executing a long running task on the Event Dispatch Thread (EDT) . The GUI can't repaint itself until the entire task is finished. Code invoked from a listener executes on the EDT. Read the section from the Swing tutorial on Concurrency for more information.

However, the solution is NOT to use the update(...) method.

The solution is to use a separate Thread for the long running task. So the main.doProcessing() statement needs to be executed in a separate Thread.

Edit2:

You need to find a text book or online tutorial to explain basic Java concepts. For example: http://docs.oracle.com/javase/tutorial/essential/concurrency/runthread.html

Or you search the forum/web for examples that use the Thread class.

In your case you can do something like:

new Thread(new Runnable()
{
    @Override
    public void run()
    {
            Main main = new Main();
            main.doProcessing();
    }
}).start();

One possible solution is to simply pipe the redirected output stream into an input stream, wrap that in a BufferedReader, and read that within a SwingWorker<Void, String> . Within the SwingWorker's doInBackground, the code that is called off the Swing event thread (the EDT or event dispatch thread), the String read in is "published", meaning it is sent to the worker's process method, a method that is called on the Swing event thread. This is then passed back into the GUI for display. For example:

Create the BufferedReader from the OutputStream:

private static BufferedReader redirectStreams() throws IOException {
    PipedOutputStream pOut = new PipedOutputStream();
    System.setOut(new PrintStream(pOut));
    PipedInputStream pIn = new PipedInputStream(pOut);
    return new BufferedReader(new InputStreamReader(pIn));
}

SwingWorker<Void, String> that reads from the BufferedReader and publish/processes the line into the GUI:

class MySwingReader extends SwingWorker<Void, String> {

    private BufferedReader reader;
    private MyRedirectGui myRedirectGui; // the GUI

    public MySwingReader(BufferedReader reader, MyRedirectGui myRedirectGui) {
        this.reader = reader;
        this.myRedirectGui = myRedirectGui;
    }

    @Override
    protected Void doInBackground() throws Exception {
        String line = null;
        while ((line = reader.readLine()) != null) {
            publish(line);
        }
        return null;
    }

    @Override
    protected void process(List<String> chunks) {
        for (String line : chunks) {
            // the append method should be a method that we give to the GUI
            myRedirectGui.append(line);
        }
    }
}

Within my GUI, I have an append method that inserts the String into the JTextArea's Document, and then scrolls the document to the bottom via setCaretPosition:

public void append(String line) {
    int offset = doc.getLength();
    try {
        doc.insertString(offset, line + "\n", null);
        textArea.setCaretPosition(offset + line.length());
    } catch (BadLocationException e) {
        e.printStackTrace();
    }
}

The whole thing:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.PrintStream;
import java.util.List;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;

public class MyRedirect {
    static private GenerateText generateText = new GenerateText();
    static private MyRedirectGui myRedirectGui = new MyRedirectGui(generateText);
    static private MySwingReader mySwingReader = null;

    public static void main(String[] args) {
        try {
            BufferedReader reader = redirectStreams();
            mySwingReader = new MySwingReader(reader, myRedirectGui);
        } catch (IOException e) {
            e.printStackTrace();
        }

        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("Redirect");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(myRedirectGui);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);

            if (mySwingReader != null) {
                mySwingReader.execute();
            }
        });
    }

    private static BufferedReader redirectStreams() throws IOException {
        PipedOutputStream pOut = new PipedOutputStream();
        System.setOut(new PrintStream(pOut));
        PipedInputStream pIn = new PipedInputStream(pOut);
        return new BufferedReader(new InputStreamReader(pIn));
    }
}

class MySwingReader extends SwingWorker<Void, String> {

    private BufferedReader reader;
    private MyRedirectGui myRedirectGui;

    public MySwingReader(BufferedReader reader, MyRedirectGui myRedirectGui) {
        this.reader = reader;
        this.myRedirectGui = myRedirectGui;
    }

    @Override
    protected Void doInBackground() throws Exception {
        String line = null;
        while ((line = reader.readLine()) != null) {
            publish(line);
        }
        return null;
    }

    @Override
    protected void process(List<String> chunks) {
        for (String line : chunks) {
            myRedirectGui.append(line);
        }
    }
}

@SuppressWarnings("serial")
class MyRedirectGui extends JPanel {
    private static final int GAP = 2;
    private JTextArea textArea = new JTextArea(30, 40);
    private Document doc = textArea.getDocument();

    public MyRedirectGui(GenerateText generateText) {
        textArea.setFocusable(false);

        textArea.setLineWrap(true);
        textArea.setWrapStyleWord(true);
        JScrollPane textAreaScroll = new JScrollPane(textArea);
        setScrollBorder(textAreaScroll, "JTextArea");

        JPanel btmPanel = new JPanel();
        btmPanel.add(new JButton(new GenerateAction(generateText)));

        setBorder(BorderFactory.createEmptyBorder(GAP, GAP, GAP, GAP));
        setLayout(new BorderLayout(GAP, GAP));
        add(textAreaScroll);
        add(btmPanel, BorderLayout.PAGE_END);
    }

    private void setScrollBorder(JScrollPane scrollPane, String title) {
        Border outsideBorder = BorderFactory.createTitledBorder(title);
        Border insideBorder = BorderFactory.createLineBorder(Color.GRAY);
        Border border = BorderFactory.createCompoundBorder(outsideBorder, insideBorder);
        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        scrollPane.setBorder(border);
    }

    public void append(String line) {
        int offset = doc.getLength();
        try {
            doc.insertString(offset, line + "\n", null);
            textArea.setCaretPosition(offset + line.length());
        } catch (BadLocationException e) {
            e.printStackTrace();
        }
    }

}

@SuppressWarnings("serial")
class GenerateAction extends AbstractAction {
    private GenerateText generateText;

    public GenerateAction(GenerateText generateText) {
        super("Generate Text");
        putValue(MNEMONIC_KEY, KeyEvent.VK_G);
        this.generateText = generateText;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        generateText.generate();
    }
}

class GenerateText {
    private static final int MAX = 10000;
    private static final long SLEEP_TIME = 1;

    public void generate() {
        new Thread(() -> {
            for (int i = 0; i < MAX; i++) {
                String line = "Printing Line............... this is line # " + i;
                System.out.println(line);
                try {
                    // careful to make sure that the loop is not too tight
                    Thread.sleep(SLEEP_TIME); // 1 mSec so that the CPU is released
                } catch (InterruptedException e) {}
            }
        }).start();
    }
}

Other problems in your code:

  • Your for loop that loops 1000 times is a tight loop and may tie up the CPU too much to allow other threads a chance to be run. Adding a small Thread.sleep(1) within it may help your program be more responsive
  • Camickr already has told you the problems you're having by using getGraphics() and update()
  • Avoid null layouts and setBounds(...) . While null layouts and setBounds() might seem to Swing newbies like the easiest and best way to create complex GUI's, the more Swing GUI'S you create the more serious difficulties you will run into when using them. They won't resize your components when the GUI resizes, they are a royal witch to enhance or maintain, they fail completely when placed in scrollpanes, they look gawd-awful when viewed on all platforms or screen resolutions that are different from the original one.
  • In particular, never set the size, the bounds, or the preferred size of a JTextArea. Doing so guarantees that it won't expand properly within the JScrollPane that holds it, making much of the text it holds unaccessable to the user.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM